diff --git a/.github/labeler.yml b/.github/labeler.yml
index 5250e321935..19c858cc81d 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -20,10 +20,8 @@ common/:
- common/**
- resources/**
-common/models/: common/models/**
-common/models/types/: common/models/types/**
-common/models/templates/: common/models/templates/**
-common/models/wordbreakers/: common/models/wordbreakers/**
+common/models/templates/: web/src/engine/predictive-text/templates/**
+common/models/wordbreakers/: web/src/engine/predictive-text/wordbreakers/**
common/resources/: resources/**
common/web/: common/web/**
diff --git a/.github/workflows/api-verification.yml b/.github/workflows/api-verification.yml
index 786e8ebad2d..6eeccdaccb0 100644
--- a/.github/workflows/api-verification.yml
+++ b/.github/workflows/api-verification.yml
@@ -1,4 +1,5 @@
name: "API Verification"
+run-name: "API Verification for ${{ github.ref_name }}"
on:
workflow_run:
workflows: [Ubuntu packaging]
diff --git a/.gitignore b/.gitignore
index 8534d172a9c..6c8fbfbec84 100644
--- a/.gitignore
+++ b/.gitignore
@@ -184,3 +184,5 @@ lcov.info
/keyman*.changes
/keyman*.tar.?z
+# flag file for build script
+.configured
diff --git a/HISTORY.md b/HISTORY.md
index 1afecba6497..9439ba6224d 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,5 +1,152 @@
# Keyman Version History
+## 18.0.122 alpha 2024-10-07
+
+* feat(mac): both option keys generate right alt if no left alt mapping (#12458)
+* chore(common): improve configuration detection for hextobin (#12481)
+
+## 18.0.121 alpha 2024-10-03
+
+* docs(developer): Fix image links in help (#12488)
+* fix(oem/fv): Update keyboard versions and names for fv_all.kmp 13.1 (#12486)
+* feat(common): unified XML parser/writer (#12482)
+
+## 18.0.120 alpha 2024-10-02
+
+* chore(common): Add note on troubleshooting website errors (#12487)
+* docs(common): mention `KEYMAN_USE_NVM` in minimum versions doc (#12490)
+* docs(web): fix paths to several help pages (#12491)
+* docs(web): fix structure of test document (#12492)
+
+## 18.0.119 alpha 2024-09-28
+
+* refactor(developer): copy dev 17.0 help into repo (#12427)
+* fix(developer): warn before importing over touch layout (#12478)
+* chore(linux): display branch name with API verification (#12480)
+* docs(core): Update kmx-plus-file-format.md (#12479)
+
+## 18.0.118 alpha 2024-09-26
+
+* chore(developer): add context/options (#11566)
+* chore(deps-dev): bump rollup from 4.16.4 to 4.22.4 (#12462)
+* fix(developer): ignore excess whitespace in `` attribute (#12468)
+* refactor(common): move help into common prod/docs/help folders (#12424)
+* fix(developer): publish developer-utils package during build (#12471)
+* fix(linux): ignore additional C++ symbol in API check (#12474)
+
+## 18.0.117 alpha 2024-09-25
+
+* docs(common): Document how to skip generating CDN on websites (#12446)
+* fix(android): Remove toggle for "Always Show Banner" (#12430)
+* fix(android): Hide suggestion banner on password fields (#12442)
+* fix(developer): prevent invalid string ids (#12465)
+
+## 18.0.116 alpha 2024-09-20
+
+* change(mac): remove verbose logging option (#12431)
+* chore(common): Allow to build offline (#12439)
+
+## 18.0.115 alpha 2024-09-19
+
+* chore(common): detect ssh remotes in git hooks (#12437)
+* fix(common): add proper configure output for hextobin (#12440)
+* fix(core): add missing dependency for core (#12438)
+* chore(developer): remove .js output from LDML compiler (#12432)
+
+## 18.0.114 alpha 2024-09-17
+
+* fix(developer): rewrite ldml visual keyboard compiler (#12402)
+* fix(developer): check vars string usage before definition (#12404)
+* change(mac): remove 'Always show OSK' option (#12355)
+
+## 18.0.113 alpha 2024-09-16
+
+* test(developer): kmcmplib compiler unit tests 3 (#11990)
+
+## 18.0.112 alpha 2024-09-14
+
+* chore(deps): bump express from 4.19.2 to 4.20.0 (#12396)
+
+## 18.0.111 alpha 2024-09-13
+
+* chore(common): Update crowdin strings for Italian (#12408)
+* fix(common): correct offsets in KMX+ spec (#12350)
+* fix(android): Add gating to setLongpressDelay() (#12410)
+
+## 18.0.110 alpha 2024-09-12
+
+* chore(common): Update to Unicode 16.0 (#12393)
+* refactor(web): move `common/web/es-bundling` → `web/src/tools/es-bundling` (#12389)
+* refactor(web): move `common/web/eslint` → `common/tools/eslint` (#12390)
+* refactor(web): move sentry-manager → `web/src/engine/sentry-manager` (#12397)
+* refactor(web): merge `device-detect` with `web/src/engine/main` (#12399)
+* chore(web): allow to run unit tests in vscode test explorer (#12400)
+* fix(developer): index() requires comma between parameters in kmcmplib compiler (#12328)
+
+## 18.0.109 alpha 2024-09-11
+
+* chore(common): Update history with 17.0.329 stable (#12394)
+* refactor(web): move `model/templates` to `web/src/engine/predictive/text` (#12382)
+* refactor(web): move `common/models` to `web/src/engine/predictive-text` (#12383)
+* refactor(web): move `common/web/utils` to `web/src/engine/common/web-utils/` (#12384)
+
+## 18.0.108 alpha 2024-09-10
+
+* docs(android): Update help docs (#12367)
+* fix(developer): fix building with Ubuntu 24.04 (#12379)
+* refactor(android): Move build-publish.sh to builder script (#12351)
+* fix(android): Separate `publishSentry` Gradle task to publish symbols to Sentry (#12358)
+* refactor(web): move `model/types` to `web/types` (#12370)
+
+## 18.0.107 alpha 2024-09-09
+
+* fix(android): Update Text Size menu icons for RTL support (#12290)
+
+## 18.0.106 alpha 2024-09-06
+
+* feat(windows): add right modifier included in hotkey optional functionality (#12259)
+* fix(android): Skip language counts for lexical-model packages (#12361)
+* docs(web): fix link to documentation page (#12369)
+
+## 18.0.105 alpha 2024-09-05
+
+* chore(common): Fix missing entries in HISTORY.md (#12352)
+* docs(android): Add in-app help for adjusting longpress delay (#12359)
+* fix(mac): avoid crash on startup with macOS 10.15 (Catalina) (#12354)
+* chore(oem/fv): Update to fv_all 13.0 (#12362)
+* feat(windows): Add two new strings for SIL Global name instead of SIL International (#12327)
+
+## 18.0.104 alpha 2024-09-03
+
+* fix(mac): display package info after keyboard installation (#12326)
+
+## 18.0.103 alpha 2024-09-02
+
+* feat(windows): Remove hotkey related feature flags (#12252)
+* feat(windows): update SIL logo for Windows UI (#12250)
+
+## 18.0.102 alpha 2024-08-30
+
+* docs(web): add documentation comments for touch layout interfaces (#12314)
+* change(mac): store data in Library directory instead of Documents (#12106)
+* change(mac): store partial path in UserDefaults (#12144)
+
+## 18.0.101 alpha 2024-08-29
+
+* refactor(web): move `gesture-recognizer` → `gesture-processor` (#12194)
+* docs(core): fix a typo in the KMX+ doc (#12302)
+* chore(web): remove obsolete comment (#12304)
+* chore(android,ios): Update FirstVoices keyboards to fv_all.kmp 12.15 (#12300)
+* fix(developer): make LDML import path consistent for all bundlings of kmc (#12280)
+* fix(core): properly support 'other' modifier state with `uint32_t` type (#12281)
+* chore(windows): fix typo in environment.inc.sh (#12286)
+* fix(android): Check in material-stepper as internal Maven dependency (#12267)
+* docs(core): improve formatting of KMX+ doc (#12303)
+* fix(android): Prioritize certain actions over multi-line for ENTER key (#12315)
+* fix(linux): add `keymanFacename` to .ldml file (#12277)
+* chore(common): Update crowdin strings for Czech (#12316)
+* fix(web): prevent unintuitive space-output blocking for mid-context suggestions (#12313)
+
## 18.0.100 alpha 2024-08-28
* fix(windows): check IM window will be in a visible location (#11967)
@@ -8,8 +155,8 @@
* feat(web): import the generator for the pred-text wordbreaker's Unicode-property data-table (#10690)
* feat(web): optimize the wordbreaker data table for filesize and ease of first-load parsing (#10692)
-* (#12297)
-* (#12115)
+* fix(web): fixes wordbreaker test import path (#12297)
+* feat(web): enable utf8 charset encoding for the build artifacts (#12115)
## 18.0.98 alpha 2024-08-26
@@ -54,11 +201,11 @@
## 18.0.93 alpha 2024-08-20
-* (#12188)
+* refactor(web): remove engine/interfaces dependency on engine/js-processor (#12188)
* fix(web): fix malformed reversion display strings (#12201)
* feat(android): Add menu to specify long-press delay (#12170)
* feat(android): Pass longpress delay to KeymanWeb (#12185)
-* (#12223)
+* fix(core): set mac build version for meson cli build to 10.13 (#12223)
## 18.0.92 alpha 2024-08-19
@@ -714,6 +861,29 @@
* chore(common): move to 18.0 alpha (#10713)
* chore: move to 18.0 alpha
+## 17.0.329 stable 2024-09-09
+
+* chore(android,ios): Add ojibwa ifinal/rdot keyboards to FirstVoices (#12020)
+* change(web): revert #11174, which loads keyboards before initializing the OSK (#12040)
+* fix(web): unrevert #11258, leaving OSK hidden before instructed to display (#12058)
+* chore(common): use `nvm` to select version of node for builds (#12074)
+* fix(developer): ignore scan code if zero in debugger (#12182)
+* fix(developer): enforce presence of Version field when FollowKeyboardVersion is not set, in package compiler (#12206)
+* fix(developer): enforce presence of kps Info.Description field in info compilers (#12207)
+* fix(web): disable fat-finger data use when mayCorrect = false (#12226)
+* chore(common): allow build agents to automatically select emsdk version, and enable support for 3.1.60+ (#12245)
+* fix(web): fix documentation-keyboard spacebar-text scaling (#12240)
+* fix(core): set mac build version for meson cli build to 10.13 (#12246)
+* change(ios): defer registration of fonts past initialization (#12241)
+* chore(android,ios): Update FirstVoices keyboards to 12.15 (#12301)
+* fix(core): properly support 'other' modifier state with `uint32_t` type (#12285)
+* fix(developer): find last matching key in LDML key bag when building KVK (#12284)
+* fix(android): check in material-stepper as internal Maven dependency (#12324)
+* fix(linux): add `keymanFacename` to .ldml file (#12283)
+* chore(oem/fv): Update to fv_all 13.0 (#12363)
+* fix(mac): avoid crash on startup with macOS 10.15 (Catalina) (#12364)
+* fix(android): skip language counts for lexical-model packages (#12368)
+
## 17.0.328 stable 2024-07-27
* fix(web): add nullish test in setOsk (#12041)
diff --git a/VERSION.md b/VERSION.md
index 5e9ac7140c0..fa985f788c5 100644
--- a/VERSION.md
+++ b/VERSION.md
@@ -1 +1 @@
-18.0.101
\ No newline at end of file
+18.0.123
\ No newline at end of file
diff --git a/android/KMAPro/build-play-store-notes.inc.sh b/android/KMAPro/build-play-store-notes.inc.sh
index 04fa8764179..56b9a92326e 100755
--- a/android/KMAPro/build-play-store-notes.inc.sh
+++ b/android/KMAPro/build-play-store-notes.inc.sh
@@ -24,7 +24,7 @@ function generateReleaseNotes() {
# Pad release notes if whatsnew.md doesn't have any line items
# Play Store release notes have a limit of 500 characters
local DEFAULT_RELEASE_NOTE="* Additional bug fixes and improvements"
- local FILTERED_LINES=$( grep '^\s*\*.*$' "$KEYMAN_ROOT/android/help/about/whatsnew.md" || [[ $? == 1 ]] ) # Continue if grep has no matches
+ local FILTERED_LINES=$( grep '^\s*\*.*$' "$KEYMAN_ROOT/android/docs/help/about/whatsnew.md" || [[ $? == 1 ]] ) # Continue if grep has no matches
if [ -z "$FILTERED_LINES" ]; then
FILTERED_LINES="$DEFAULT_RELEASE_NOTE"
builder_warn "Warning: whatsnew.md empty so using default release note: '$FILTERED_LINES'"
@@ -32,7 +32,7 @@ function generateReleaseNotes() {
# Change IFS to new line
local old_IFS="${IFS}"
- IFS=$'\n'
+ IFS=$'\n'
for line in $FILTERED_LINES
do
local CHARS_IN_RELEASE_NOTES=$( wc -m < "$PLAY_RELEASE_NOTES" )
diff --git a/android/KMAPro/build.gradle b/android/KMAPro/build.gradle
index b1f2d240a72..5614fcae6a6 100644
--- a/android/KMAPro/build.gradle
+++ b/android/KMAPro/build.gradle
@@ -2,8 +2,10 @@
buildscript {
repositories {
google()
- jcenter()
mavenCentral()
+ flatDir {
+ dirs "kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/"
+ }
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
@@ -11,16 +13,16 @@ buildscript {
classpath 'io.sentry:sentry-android-gradle-plugin:4.6.0'
classpath 'name.remal:gradle-plugins:1.5.0'
- // From jcenter() which could be sunset in future
+ // From jcenter() which was deprecated August 2024
// https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/
- classpath 'com.stepstone.stepper:material-stepper:4.3.1'
+ classpath 'com.stepstone.stepper:material-stepper:4.3.1@aar'
}
}
allprojects {
repositories {
+ maven { url uri("${projectDir}/libs") }
google()
- jcenter()
mavenCentral()
maven { url "https://jitpack.io" }
}
diff --git a/android/KMAPro/build.sh b/android/KMAPro/build.sh
index e9a51062106..1eafe1e6671 100755
--- a/android/KMAPro/build.sh
+++ b/android/KMAPro/build.sh
@@ -10,6 +10,8 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
. "$KEYMAN_ROOT/resources/build/build-help.inc.sh"
. "$KEYMAN_ROOT/resources/build/build-download-resources.sh"
+. "$KEYMAN_ROOT/android/KMAPro/build-play-store-notes.inc.sh"
+
# ################################ Main script ################################
# Definition of global compile constants
@@ -25,6 +27,7 @@ builder_describe "Builds Keyman for Android app." \
"configure" \
"build" \
"test Runs lint and unit tests." \
+ "publish Publishes symbols to Sentry and the APK to the Play Store." \
"--ci Don't start the Gradle daemon. For CI" \
"--upload-sentry Upload to sentry"
@@ -109,3 +112,13 @@ if builder_start_action test; then
builder_finish_action success test
fi
+
+if builder_start_action publish; then
+ # Copy Release Notes
+ generateReleaseNotes
+
+ # Publish symbols and Keyman for Android to Play Store
+ ./gradlew $DAEMON_FLAG publishSentry publishReleaseApk
+
+ builder_finish_action success publish
+fi
diff --git a/android/KMAPro/kMAPro/build.gradle b/android/KMAPro/kMAPro/build.gradle
index 7eec37bcefa..11ec4efd2db 100644
--- a/android/KMAPro/kMAPro/build.gradle
+++ b/android/KMAPro/kMAPro/build.gradle
@@ -105,12 +105,17 @@ android {
}
// how to configure the sentry android gradle plugin
-sentry {
- // Disables or enables the automatic configuration of Native symbols
- uploadNativeSymbols = true
-
- // Does or doesn't include the source code of native code for Sentry
- includeNativeSources = true
+task publishSentry {
+ doLast {
+ println 'Publishing Keyman symbols to Sentry'
+ sentry {
+ // Disables or enables the automatic configuration of Native symbols
+ uploadNativeSymbols = true
+
+ // Does or doesn't include the source code of native code for Sentry
+ includeNativeSources = true
+ }
+ }
}
String env_keys_json_file = System.getenv("keys_json_file")
@@ -145,7 +150,6 @@ repositories {
dirs 'libs'
}
google()
- jcenter()
}
dependencies {
diff --git a/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1-javadoc.jar b/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1-javadoc.jar
new file mode 100644
index 00000000000..895d772c397
Binary files /dev/null and b/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1-javadoc.jar differ
diff --git a/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1-sources.jar b/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1-sources.jar
new file mode 100644
index 00000000000..a3c41483871
Binary files /dev/null and b/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1-sources.jar differ
diff --git a/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1.aar b/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1.aar
new file mode 100644
index 00000000000..9bc9a3f05cd
Binary files /dev/null and b/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1.aar differ
diff --git a/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1.pom b/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1.pom
new file mode 100644
index 00000000000..5bd1fc5d399
--- /dev/null
+++ b/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1.pom
@@ -0,0 +1,90 @@
+
+
+ 4.0.0
+ com.stepstone.stepper
+ material-stepper
+ 4.3.1
+ aar
+ Android Material Stepper
+ This library allows to use Material steppers inside Android applications.
+ https://github.com/stepstone-tech/android-material-stepper
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+
+
+
+
+ zawadz88
+ Piotr Zawadzki
+ piotr.zawadzki@stepstone.com
+
+
+
+ https://github.com/stepstone-tech/android-material-stepper.git
+ https://github.com/stepstone-tech/android-material-stepper.git
+ https://github.com/stepstone-tech/android-material-stepper
+
+
+
+ com.android.support
+ appcompat-v7
+ 25.4.0
+ compile
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+ org.mockito
+ mockito-core
+ 2.7.21
+ test
+
+
+ com.squareup.assertj
+ assertj-android
+ 1.1.1
+ test
+
+
+ org.robolectric
+ robolectric
+ 3.3.1
+ test
+
+
+ httpclient
+ org.apache.httpcomponents
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jre7
+ 1.1.4-3
+ test
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+ 1.1.4-3
+ test
+
+
+ com.nhaarman
+ mockito-kotlin
+ 1.4.0
+ test
+
+
+
diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java
index 2ecf62fe099..e79cb917ff3 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java
@@ -31,8 +31,6 @@ public class AdjustLongpressDelayActivity extends BaseActivity {
// Keeps track of the adjusted longpress delay time for saving.
// Internally use milliseconds, but GUI displays seconds
private static int currentDelayTimeMS = KMManager.KMDefault_LongpressDelay; // ms
- private static int minLongpressTime = 300; // ms
- private static int maxLongpressTime = 1500; // ms
private static int delayTimeIncrement = 200; // ms
/**
@@ -112,7 +110,7 @@ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
findViewById(R.id.delayTimeDownButton).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- if (currentDelayTimeMS > minLongpressTime) {
+ if (currentDelayTimeMS > KMManager.KMMinimum_LongpressDelay) {
currentDelayTimeMS -= delayTimeIncrement;
seekBar.setProgress(delayTimeToProgress());
}
@@ -122,7 +120,7 @@ public void onClick(View v) {
findViewById(R.id.delayTimeUpButton).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- if (currentDelayTimeMS < maxLongpressTime) {
+ if (currentDelayTimeMS < KMManager.KMMaximum_LongpressDelay) {
currentDelayTimeMS += delayTimeIncrement;
seekBar.setProgress(delayTimeToProgress());
}
diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsActivity.java
index 86494e929b0..7e6dcbe5b15 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsActivity.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsActivity.java
@@ -10,7 +10,6 @@
public class KeymanSettingsActivity extends BaseActivity {
protected static final String installedLanguagesKey = "InstalledLanguages";
protected static final String installKeyboardOrDictionaryKey = "InstallKeyboardOrDictionary";
- protected static final String showBannerKey = "ShowBanner";
protected static final String sendCrashReport = "SendCrashReport";
public static final String spacebarTextKey = "SpacebarText";
public static final String hapticFeedbackKey = "HapticFeedback";
diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java
index 95bb7774b0b..59ac59e2f86 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java
@@ -178,12 +178,6 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
editor.putBoolean(KeymanSettingsActivity.showBannerKey, isChecked);
as part of the default onClick() used by SwitchPreference.
*/
- SwitchPreference bannerPreference = new SwitchPreference(context);
- bannerPreference.setKey(KeymanSettingsActivity.showBannerKey);
- bannerPreference.setTitle(getString(R.string.show_banner));
- bannerPreference.setSummaryOn(getString(R.string.show_banner_on));
- bannerPreference.setSummaryOff(getString(R.string.show_banner_off));
-
SwitchPreference getStartedPreference = new SwitchPreference(context);
getStartedPreference.setKey(GetStartedActivity.showGetStartedKey);
getStartedPreference.setTitle(String.format(getString(R.string.show_get_started), getString(R.string.get_started)));
@@ -207,7 +201,6 @@ as part of the default onClick() used by SwitchPreference.
screen.addPreference(spacebarTextPreference);
screen.addPreference(hapticFeedbackPreference);
- screen.addPreference(bannerPreference);
screen.addPreference(getStartedPreference);
screen.addPreference(sendCrashReportPreference);
diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java
index 3806d5f4b87..161d9663089 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java
@@ -112,7 +112,8 @@ public void onCreate(Bundle savedInstanceState) {
// Number of languages associated with the first keyboard in a keyboard package.
// lexical-model packages will be 0
- final int languageCount = kmpProcessor.getLanguageCount(pkgInfo, PackageProcessor.PP_KEYBOARDS_KEY, 0);
+ final int languageCount = (keyboardCount > 0) ?
+ kmpProcessor.getLanguageCount(pkgInfo, PackageProcessor.PP_KEYBOARDS_KEY, 0) : 0;
// Sanity check for keyboard packages
if (pkgTarget.equals(PackageProcessor.PP_TARGET_KEYBOARDS)) {
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_light_action_textsize.png b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_light_action_textsize.png
index 3809ef23536..9c8e3354e6b 100644
Binary files a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_light_action_textsize.png and b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_light_action_textsize.png differ
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-hdpi/ic_light_action_textsize.png b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-hdpi/ic_light_action_textsize.png
new file mode 100644
index 00000000000..6764f528f29
Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-hdpi/ic_light_action_textsize.png differ
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-mdpi/ic_light_action_textsize.png b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-mdpi/ic_light_action_textsize.png
new file mode 100644
index 00000000000..a31690d1271
Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-mdpi/ic_light_action_textsize.png differ
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-xhdpi/ic_light_action_textsize.png b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-xhdpi/ic_light_action_textsize.png
new file mode 100644
index 00000000000..ca9829d1788
Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-xhdpi/ic_light_action_textsize.png differ
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-xxhdpi/ic_light_action_textsize.png b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-xxhdpi/ic_light_action_textsize.png
new file mode 100644
index 00000000000..d15adad0acd
Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-xxhdpi/ic_light_action_textsize.png differ
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/textsize_decrease.xml b/android/KMAPro/kMAPro/src/main/res/drawable/textsize_decrease.xml
new file mode 100644
index 00000000000..3e8ea253f78
--- /dev/null
+++ b/android/KMAPro/kMAPro/src/main/res/drawable/textsize_decrease.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/textsize_increase.xml b/android/KMAPro/kMAPro/src/main/res/drawable/textsize_increase.xml
new file mode 100644
index 00000000000..b51b51139b5
--- /dev/null
+++ b/android/KMAPro/kMAPro/src/main/res/drawable/textsize_increase.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/android/KMAPro/kMAPro/src/main/res/layout/text_size_controller.xml b/android/KMAPro/kMAPro/src/main/res/layout/text_size_controller.xml
index c250b1bf072..cfd36374deb 100644
--- a/android/KMAPro/kMAPro/src/main/res/layout/text_size_controller.xml
+++ b/android/KMAPro/kMAPro/src/main/res/layout/text_size_controller.xml
@@ -15,7 +15,7 @@
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:contentDescription="@string/ic_text_size_down"
- android:src="@drawable/ic_action_decrement" />
+ android:src="@drawable/textsize_decrease" />
+ android:src="@drawable/textsize_increase" />
diff --git a/android/KMAPro/kMAPro/src/main/res/values-cs-rCZ/strings.xml b/android/KMAPro/kMAPro/src/main/res/values-cs-rCZ/strings.xml
index bec7931da85..97da3115df7 100644
--- a/android/KMAPro/kMAPro/src/main/res/values-cs-rCZ/strings.xml
+++ b/android/KMAPro/kMAPro/src/main/res/values-cs-rCZ/strings.xml
@@ -33,6 +33,8 @@
Velikost textu nahoru
Velikost textu dolů
+
+ Posuvník velikosti textu
\nVeškerý text bude vymazán\n
@@ -51,6 +53,7 @@
Pro instalaci balíčků klávesnice povolte klávesnici oprávnění ke čtení externího úložiště.
Žádost o oprávnění k úložišti byla zamítnuta. Může selhat instalace balíčku klávesnice
+ Žádost o oprávnění k úložišti se nezdařila. Zkuste nastavení Keyman - Instalovat z místního souboru
Nastavení
@@ -67,6 +70,8 @@
Upravit výšku klávesnice
+ Upravit zpoždění dlouhého stisknutí
+
Titulek mezerníku
Keyboard
@@ -85,7 +90,7 @@
Nezobrazovat titulek v mezerníku
- Vibrate when typing
+ Při psaní vibrovat
Vždy zobrazovat banner
@@ -122,6 +127,14 @@
Otočit zařízení pro nastavení na výšku a na šířku
Obnovit výchozí nastavení
+
+ Doba zpoždění: %1$.1f sekund
+
+ Prodloužit dobu zpoždění
+
+ Zkrátit dobu zpoždění
+
+ Posuvník doby zpoždění dlouhého stisknutí
Hledat nebo zadat URL
@@ -157,7 +170,7 @@
Neplatná/chybějící metadata v balíčku
- Keyboard requires a newer version of Keyman
+ Klávesnice vyžaduje novější verzi Keyman
- Unable to launch web browser
+ Nelze spustit webový prohlížeč
diff --git a/android/KMAPro/kMAPro/src/main/res/values-it-rIT/strings.xml b/android/KMAPro/kMAPro/src/main/res/values-it-rIT/strings.xml
index b3d15fc6317..a475ae37255 100644
--- a/android/KMAPro/kMAPro/src/main/res/values-it-rIT/strings.xml
+++ b/android/KMAPro/kMAPro/src/main/res/values-it-rIT/strings.xml
@@ -1,5 +1,6 @@
+
Condividi
@@ -32,6 +33,8 @@
Dimensione testo in alto
Dimensione del testo giù
+
+ Cursore dimensione testo
\nTutto il testo sarà cancellato\n
@@ -50,6 +53,7 @@
Per installare pacchetti di tastiera, consentire a Keyman di leggere la memoria esterna.
La richiesta di autorizzazione all\'archiviazione è stata negata. Potrebbe non essere stato possibile installare il pacchetto tastiera
+ Richiesta di autorizzazione archiviazione non riuscita. Prova Impostazioni Keyman - Installa da file locale
Impostazioni
@@ -64,6 +68,8 @@
Regola altezza tastiera
+ Adjust longpress delay
+
Didascalia barra spaziatrice
Keyboard
@@ -82,7 +88,7 @@
Non mostrare la didascalia sulla barra spaziatrice
- Vibrate when typing
+ Vibra durante la digitazione
Mostra sempre il banner
@@ -119,6 +125,14 @@
Ruota il dispositivo per regolare verticale e orizzontale
Ripristina impostazioni predefinite
+
+ Delay Time: %1$.1f seconds
+
+ Delay time longer
+
+ Delay time shorter
+
+ Longpress delay time slider
Cerca o digita URL
@@ -154,7 +168,7 @@
Metadati non validi/mancanti nel pacchetto
- Keyboard requires a newer version of Keyman
+ La tastiera richiede una nuova versione di Keyman
- Unable to launch web browser
+ Impossibile avviare il browser web
diff --git a/android/KMAPro/kMAPro/src/main/res/values/strings.xml b/android/KMAPro/kMAPro/src/main/res/values/strings.xml
index a1f9e7240d7..01546f91ba5 100644
--- a/android/KMAPro/kMAPro/src/main/res/values/strings.xml
+++ b/android/KMAPro/kMAPro/src/main/res/values/strings.xml
@@ -147,13 +147,13 @@
Vibrate when typing
-
+
Always show banner
-
+
To be implemented
-
+
When off, only shown when predictive text is enabled
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java
index b80f6766a33..1e28e4af0b3 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java
@@ -164,7 +164,7 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) {
// appContext instead of context?
SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
boolean modelPredictionPref = false;
- if (KMManager.currentLexicalModel != null) {
+ if (!KMManager.getMayPredictOverride() && KMManager.currentLexicalModel != null) {
modelPredictionPref = prefs.getBoolean(KMManager.getLanguagePredictionPreferenceKey(KMManager.currentLexicalModel.get(KMManager.KMKey_LanguageID)), true);
}
KMManager.setBannerOptions(modelPredictionPref);
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java
index b921ac6c0ad..592f4be2a15 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java
@@ -310,8 +310,11 @@ public enum EnterModeType {
public static final String KMDefault_DictionaryVersion = "0.1.4";
public static final String KMDefault_DictionaryKMP = KMDefault_DictionaryPackageID + FileUtils.MODELPACKAGE;
- // Default KeymanWeb longpress delay in milliseconds
+ // Default KeymanWeb longpress delay constants in milliseconds
public static final int KMDefault_LongpressDelay = 500;
+ public static final int KMMinimum_LongpressDelay = 300;
+ public static final int KMMaximum_LongpressDelay = 1500;
+
// Keyman files
protected static final String KMFilename_KeyboardHtml = "keyboard.html";
@@ -1274,17 +1277,13 @@ public boolean accept(File pathname) {
/**
* Sets enterMode which specifies how the System keyboard ENTER key is handled
*
- * @param imeOptions EditorInfo.imeOptions
- * @param inputType InputType
+ * @param imeOptions EditorInfo.imeOptions used to determine the action
+ * @param inputType InputType used to determine if the text field is multi-line
*/
public static void setEnterMode(int imeOptions, int inputType) {
- if ((inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) != 0) {
- enterMode = EnterModeType.NEWLINE;
- return;
- }
-
- int imeActions = imeOptions & EditorInfo.IME_MASK_ACTION;
EnterModeType value = EnterModeType.DEFAULT;
+ int imeActions = imeOptions & EditorInfo.IME_MASK_ACTION;
+ boolean isMultiLine = (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) != 0;
switch (imeActions) {
case EditorInfo.IME_ACTION_GO:
@@ -1296,7 +1295,8 @@ public static void setEnterMode(int imeOptions, int inputType) {
break;
case EditorInfo.IME_ACTION_SEND:
- value = EnterModeType.SEND;
+ value = isMultiLine ?
+ EnterModeType.NEWLINE :EnterModeType.SEND;
break;
case EditorInfo.IME_ACTION_NEXT:
@@ -1304,7 +1304,8 @@ public static void setEnterMode(int imeOptions, int inputType) {
break;
case EditorInfo.IME_ACTION_DONE:
- value = EnterModeType.DONE;
+ value = isMultiLine ?
+ EnterModeType.NEWLINE : EnterModeType.DONE;
break;
case EditorInfo.IME_ACTION_PREVIOUS:
@@ -1312,7 +1313,8 @@ public static void setEnterMode(int imeOptions, int inputType) {
break;
default:
- value = EnterModeType.DEFAULT;
+ value = isMultiLine ?
+ EnterModeType.NEWLINE : EnterModeType.DEFAULT;
}
enterMode = value;
@@ -2098,14 +2100,22 @@ public static int getLongpressDelay() {
/**
* Set the longpress delay (in milliseconds) as a stored preference.
+ * Valid range is 300 ms to 1500 ms. Returns true if the preference is successfully stored.
* @param longpressDelay - int longpress delay in milliseconds
+ * @return boolean
*/
- public static void setLongpressDelay(int longpressDelay) {
+ public static boolean setLongpressDelay(int longpressDelay) {
+ if (longpressDelay < KMMinimum_LongpressDelay || longpressDelay > KMMaximum_LongpressDelay) {
+ return false;
+ }
+
SharedPreferences prefs = appContext.getSharedPreferences(
appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(KMKey_LongpressDelay, longpressDelay);
editor.commit();
+
+ return true;
}
/**
diff --git a/android/KMEA/app/src/main/res/values-cs-rCZ/strings.xml b/android/KMEA/app/src/main/res/values-cs-rCZ/strings.xml
index 1bcc7926603..db45a6706e0 100644
--- a/android/KMEA/app/src/main/res/values-cs-rCZ/strings.xml
+++ b/android/KMEA/app/src/main/res/values-cs-rCZ/strings.xml
@@ -49,7 +49,7 @@
Aktualizovat
- No internet connection
+ Žádné připojení k internetu
Nelze se připojit k Keyman serveru!
@@ -88,7 +88,7 @@
Chyba v klávesnici %1$s:%2$s pro jazyk %3$s.
Kontroluji související slovník ke stažení
- Cannot connect to Keyman server to check for associated dictionary to download
+ Nelze se připojit ke Keyman serveru a zkontrolovat, zda je možné stáhnout přidružený slovník
Chcete stáhnout nejnovější verzi tohoto slovníku?
@@ -111,7 +111,7 @@
Stahování slovníku začalo na pozadí
Vybraný slovník se již stahuje; zkuste to prosím za chvíli znovu!
-
+
Stahování slovníku je dokončeno.
Stahování se nezdařilo
@@ -123,7 +123,7 @@
"Všechny zdroje jsou aktuální!"
Aktualizace jednoho nebo více zdrojů selhala!
-
+
Zdroje byly úspěšně aktualizovány!
Verze slovníku
@@ -132,11 +132,11 @@
Chcete odstranit tento slovník?
- Dictionary deleted
+ Slovník smazán
Klávesnice %1$s nainstalována
- Keyboard deleted
+ Klávesnice smazána
Povolit opravy
@@ -144,14 +144,14 @@
Slovník
- - Dictionary
- - Dictionaries
- - Dictionaries
- - Dictionaries
+ - Slovník
+ - Slovníky
+ - Slovníků
+ - Slovníků
Zkontrolovat dostupný slovník
- Check for dictionaries online
+ Kouknout se na slovníky online
Slovník: %1$s
@@ -176,5 +176,5 @@
Klepnutím sem změníte klávesnici
- Unable to launch web browser
+ Nelze spustit webový prohlížeč
diff --git a/android/KMEA/app/src/main/res/values-it-rIT/strings.xml b/android/KMEA/app/src/main/res/values-it-rIT/strings.xml
index 2b9c950cc67..da1e3531a2b 100644
--- a/android/KMEA/app/src/main/res/values-it-rIT/strings.xml
+++ b/android/KMEA/app/src/main/res/values-it-rIT/strings.xml
@@ -45,7 +45,7 @@
Aggiorna
- No internet connection
+ Nessuna connessione internet
Impossibile connettersi al server Keyman!
@@ -84,7 +84,7 @@
Errore nella tastiera %1$s:%2$s per %3$s lingua.
Controllo del dizionario associato da scaricare
- Cannot connect to Keyman server to check for associated dictionary to download
+ Non è possibile connettersi al server Keyman per verificare dizionari associati da scaricare
Vuoi scaricare l\'ultima versione di questo dizionario?
@@ -107,7 +107,7 @@
Scaricamento del dizionario avviato in background
Il dizionario selezionato è già in download; riprova tra un momento!
-
+
Il download del dizionario è terminato.
Download non riuscito
@@ -119,7 +119,7 @@
"Tutte le risorse sono aggiornate!"
Una o più risorse non sono state aggiornate!
-
+
Risorse aggiornate con successo!
Versione dizionario
@@ -128,11 +128,11 @@
Vuoi eliminare questo dizionario?
- Dictionary deleted
+ Dizionario eliminato
%1$s tastiera installata
- Keyboard deleted
+ Tastiera eliminata
Abilita correzioni
@@ -140,12 +140,12 @@
Dizionario
- - Dictionary
- - Dictionaries
+ - Dizionario
+ - Dizionari
Controlla il dizionario disponibile
- Check for dictionaries online
+ Controlla i dizionari online
Dizionario: %1$s
@@ -168,5 +168,5 @@
Tocca qui per cambiare la tastiera
- Unable to launch web browser
+ Impossibile avviare il browser web
diff --git a/android/KMEA/build.sh b/android/KMEA/build.sh
index 878b54797dc..29cb519cf56 100755
--- a/android/KMEA/build.sh
+++ b/android/KMEA/build.sh
@@ -23,7 +23,7 @@ JUNIT_RESULTS="##teamcity[importData type='junit' path='keyman\android\KMEA\app\
builder_describe "Builds Keyman Engine for Android." \
"@/web/src/app/webview" \
- "@/common/web/sentry-manager" \
+ "@/web/src/engine/sentry-manager" \
"clean" \
"configure" \
"build" \
@@ -84,7 +84,7 @@ if builder_start_action build:engine; then
cp "$KEYMAN_WEB_ROOT/build/app/resources/osk/keymanweb-osk.ttf" "$ENGINE_ASSETS/keymanweb-osk.ttf"
cp "$KEYMAN_ROOT/node_modules/@sentry/browser/build/bundle.min.js" "$ENGINE_ASSETS/sentry.min.js"
- cp "$KEYMAN_ROOT/common/web/sentry-manager/build/lib/index.js" "$ENGINE_ASSETS/keyman-sentry.js"
+ cp "$KEYMAN_ROOT/web/src/engine/sentry-manager/build/lib/index.js" "$ENGINE_ASSETS/keyman-sentry.js"
echo "Copying es6-shim polyfill"
cp "$KEYMAN_ROOT/node_modules/es6-shim/es6-shim.min.js" "$ENGINE_ASSETS/es6-shim.min.js"
diff --git a/android/build-publish.sh b/android/build-publish.sh
deleted file mode 100755
index 29c152c993d..00000000000
--- a/android/build-publish.sh
+++ /dev/null
@@ -1,87 +0,0 @@
-#!/usr/bin/env bash
-# CI script to publish specified app APKs to the Play Store.
-# The APKs should already have been built from a separate script
-
-# set -x: Debugging use, print each statement
-# set -x
-
-## START STANDARD BUILD SCRIPT INCLUDE
-# adjust relative paths as necessary
-THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
-. "${THIS_SCRIPT%/*}/../resources/build/build-utils.sh"
-## END STANDARD BUILD SCRIPT INCLUDE
-
-. "$KEYMAN_ROOT/android/KMAPro/build-play-store-notes.inc.sh"
-
-echo Publishing APKs to Play Store
-
-#
-# Prevents 'clear' on exit of mingw64 bash shell
-#
-SHLVL=0
-
-display_usage ( ) {
- echo "build-publish.sh [-no-daemon] [-kmapro] [-fv]"
- echo
- echo "Publish app to the Play Store"
- echo " -no-daemon Don't start the Gradle daemon. Use for CI"
- echo " -kmapro Keyman for Android"
- echo " -fv First Voices"
- exit 1
-}
-
-NO_DAEMON=false
-DO_KMAPRO=false
-DO_FV=false
-
-# Parse args
-while [[ $# -gt 0 ]] ; do
- key="$1"
- case $key in
- -no-daemon)
- NO_DAEMON=true
- ;;
- -kmapro)
- DO_KMAPRO=true
- ;;
- -fv)
- DO_FV=true
- ;;
- -h|-\?)
- display_usage
- ;;
- esac
- shift # past argument
-done
-
-# Override JAVA_HOME to OpenJDK 11
-set_java_home
-
-echo
-echo "NO_DAEMON: $NO_DAEMON"
-echo "DO_KMAPRO: $DO_KMAPRO"
-echo "DO_FV: $DO_FV"
-echo
-
-if [ "$NO_DAEMON" = true ]; then
- DAEMON_FLAG=--no-daemon
-else
- DAEMON_FLAG=
-fi
-
-BUILD_FLAGS="publishReleaseApk"
-echo "BUILD_FLAGS $BUILD_FLAGS"
-
-# Publish Keyman for Android
-if [ "$DO_KMAPRO" = true ]; then
- # Copy Release Notes
- generateReleaseNotes
- cd "$KEYMAN_ROOT/android/KMAPro/"
- ./gradlew $DAEMON_FLAG $BUILD_FLAGS
-fi
-
-# Publish FV app
-if [ "$DO_FV" = true ]; then
- cd "$KEYMAN_ROOT/oem/firstvoices/android/"
- ./gradlew $DAEMON_FLAG $BUILD_FLAGS
-fi
diff --git a/android/build.sh b/android/build.sh
index 88048c7aa30..d876d882d77 100755
--- a/android/build.sh
+++ b/android/build.sh
@@ -18,7 +18,7 @@ builder_describe \
configure \
build \
test \
- "publish Publishes the APKs to the Play Store." \
+ "publish Publishes symbols to Sentry and the APKs to the Play Store." \
--ci+ \
--upload-sentry+ \
":engine=KMEA Keyman Engine for Android" \
diff --git a/android/help/about/history.md b/android/docs/help/about/history.md
similarity index 100%
rename from android/help/about/history.md
rename to android/docs/help/about/history.md
diff --git a/android/help/about/index.md b/android/docs/help/about/index.md
similarity index 100%
rename from android/help/about/index.md
rename to android/docs/help/about/index.md
diff --git a/android/help/about/privacy.md b/android/docs/help/about/privacy.md
similarity index 94%
rename from android/help/about/privacy.md
rename to android/docs/help/about/privacy.md
index 6173d6c3c39..c3dbd000eee 100644
--- a/android/help/about/privacy.md
+++ b/android/docs/help/about/privacy.md
@@ -19,7 +19,7 @@ Keyman does not use user accounts, so we are unable to identify which crash repo
### Developer Information
-Keyman is developed and published by SIL International.
+Keyman is developed and published by SIL Global.
You can contact SIL or provide any feedback using the app store.
diff --git a/android/help/about/system-requirements.md b/android/docs/help/about/system-requirements.md
similarity index 100%
rename from android/help/about/system-requirements.md
rename to android/docs/help/about/system-requirements.md
diff --git a/android/help/about/welcome.md b/android/docs/help/about/welcome.md
similarity index 100%
rename from android/help/about/welcome.md
rename to android/docs/help/about/welcome.md
diff --git a/android/docs/help/about/whatsnew.md b/android/docs/help/about/whatsnew.md
new file mode 100644
index 00000000000..500d0a9593b
--- /dev/null
+++ b/android/docs/help/about/whatsnew.md
@@ -0,0 +1,9 @@
+---
+title: What's New
+---
+
+Here are some of the new features we have added to Keyman 18.0 for Android:
+
+* New menu to adjust longpress delay time (#12170, #12185)
+* Support localizations for right-to-left languages (#12215)
+* Handle additional actions for ENTER key (#12125, #12315)
diff --git a/android/help/android_images/adjust-height.png b/android/docs/help/android_images/adjust-height.png
similarity index 100%
rename from android/help/android_images/adjust-height.png
rename to android/docs/help/android_images/adjust-height.png
diff --git a/android/help/android_images/backspace-ap.png b/android/docs/help/android_images/backspace-ap.png
similarity index 100%
rename from android/help/android_images/backspace-ap.png
rename to android/docs/help/android_images/backspace-ap.png
diff --git a/android/help/android_images/backspace-at.png b/android/docs/help/android_images/backspace-at.png
similarity index 100%
rename from android/help/android_images/backspace-at.png
rename to android/docs/help/android_images/backspace-at.png
diff --git a/android/help/android_images/blank-osk.png b/android/docs/help/android_images/blank-osk.png
similarity index 100%
rename from android/help/android_images/blank-osk.png
rename to android/docs/help/android_images/blank-osk.png
diff --git a/android/help/android_images/browser-a.png b/android/docs/help/android_images/browser-a.png
similarity index 100%
rename from android/help/android_images/browser-a.png
rename to android/docs/help/android_images/browser-a.png
diff --git a/android/help/android_images/confirm-english-dictionary-delete-ap.png b/android/docs/help/android_images/confirm-english-dictionary-delete-ap.png
similarity index 100%
rename from android/help/android_images/confirm-english-dictionary-delete-ap.png
rename to android/docs/help/android_images/confirm-english-dictionary-delete-ap.png
diff --git a/android/help/android_images/confirm-khmer-delete-ap.png b/android/docs/help/android_images/confirm-khmer-delete-ap.png
similarity index 100%
rename from android/help/android_images/confirm-khmer-delete-ap.png
rename to android/docs/help/android_images/confirm-khmer-delete-ap.png
diff --git a/android/help/android_images/delete-a.png b/android/docs/help/android_images/delete-a.png
similarity index 100%
rename from android/help/android_images/delete-a.png
rename to android/docs/help/android_images/delete-a.png
diff --git a/android/help/android_images/dist-file-browser-ap.png b/android/docs/help/android_images/dist-file-browser-ap.png
similarity index 100%
rename from android/help/android_images/dist-file-browser-ap.png
rename to android/docs/help/android_images/dist-file-browser-ap.png
diff --git a/android/help/android_images/dist-file-browser-at.png b/android/docs/help/android_images/dist-file-browser-at.png
similarity index 100%
rename from android/help/android_images/dist-file-browser-at.png
rename to android/docs/help/android_images/dist-file-browser-at.png
diff --git a/android/help/android_images/dist-install1-ap.png b/android/docs/help/android_images/dist-install1-ap.png
similarity index 100%
rename from android/help/android_images/dist-install1-ap.png
rename to android/docs/help/android_images/dist-install1-ap.png
diff --git a/android/help/android_images/dist-install1-at.png b/android/docs/help/android_images/dist-install1-at.png
similarity index 100%
rename from android/help/android_images/dist-install1-at.png
rename to android/docs/help/android_images/dist-install1-at.png
diff --git a/android/help/android_images/dist-storage-permission-ap.png b/android/docs/help/android_images/dist-storage-permission-ap.png
similarity index 100%
rename from android/help/android_images/dist-storage-permission-ap.png
rename to android/docs/help/android_images/dist-storage-permission-ap.png
diff --git a/android/help/android_images/dist-storage-permission-at.png b/android/docs/help/android_images/dist-storage-permission-at.png
similarity index 100%
rename from android/help/android_images/dist-storage-permission-at.png
rename to android/docs/help/android_images/dist-storage-permission-at.png
diff --git a/android/help/android_images/dist-url-screen-ap.png b/android/docs/help/android_images/dist-url-screen-ap.png
similarity index 100%
rename from android/help/android_images/dist-url-screen-ap.png
rename to android/docs/help/android_images/dist-url-screen-ap.png
diff --git a/android/help/android_images/dist-url-screen-at.png b/android/docs/help/android_images/dist-url-screen-at.png
similarity index 100%
rename from android/help/android_images/dist-url-screen-at.png
rename to android/docs/help/android_images/dist-url-screen-at.png
diff --git a/android/help/android_images/dl-success-ap.png b/android/docs/help/android_images/dl-success-ap.png
similarity index 100%
rename from android/help/android_images/dl-success-ap.png
rename to android/docs/help/android_images/dl-success-ap.png
diff --git a/android/help/android_images/english-dictionaries-ap.png b/android/docs/help/android_images/english-dictionaries-ap.png
similarity index 100%
rename from android/help/android_images/english-dictionaries-ap.png
rename to android/docs/help/android_images/english-dictionaries-ap.png
diff --git a/android/help/android_images/english-dictionary-info-ap.png b/android/docs/help/android_images/english-dictionary-info-ap.png
similarity index 100%
rename from android/help/android_images/english-dictionary-info-ap.png
rename to android/docs/help/android_images/english-dictionary-info-ap.png
diff --git a/android/help/android_images/english-settings-ap.png b/android/docs/help/android_images/english-settings-ap.png
similarity index 100%
rename from android/help/android_images/english-settings-ap.png
rename to android/docs/help/android_images/english-settings-ap.png
diff --git a/android/help/android_images/font-size-a.png b/android/docs/help/android_images/font-size-a.png
similarity index 100%
rename from android/help/android_images/font-size-a.png
rename to android/docs/help/android_images/font-size-a.png
diff --git a/android/help/android_images/get-started-a.png b/android/docs/help/android_images/get-started-a.png
similarity index 100%
rename from android/help/android_images/get-started-a.png
rename to android/docs/help/android_images/get-started-a.png
diff --git a/android/help/android_images/globe-ap.png b/android/docs/help/android_images/globe-ap.png
similarity index 100%
rename from android/help/android_images/globe-ap.png
rename to android/docs/help/android_images/globe-ap.png
diff --git a/android/help/android_images/globe-at.png b/android/docs/help/android_images/globe-at.png
similarity index 100%
rename from android/help/android_images/globe-at.png
rename to android/docs/help/android_images/globe-at.png
diff --git a/android/help/android_images/hide-keyboard-ap.png b/android/docs/help/android_images/hide-keyboard-ap.png
similarity index 100%
rename from android/help/android_images/hide-keyboard-ap.png
rename to android/docs/help/android_images/hide-keyboard-ap.png
diff --git a/android/help/android_images/hide-keyboard-at.png b/android/docs/help/android_images/hide-keyboard-at.png
similarity index 100%
rename from android/help/android_images/hide-keyboard-at.png
rename to android/docs/help/android_images/hide-keyboard-at.png
diff --git a/android/help/android_images/ic_cloud_download.png b/android/docs/help/android_images/ic_cloud_download.png
similarity index 100%
rename from android/help/android_images/ic_cloud_download.png
rename to android/docs/help/android_images/ic_cloud_download.png
diff --git a/android/help/android_images/ic_content_add.png b/android/docs/help/android_images/ic_content_add.png
similarity index 100%
rename from android/help/android_images/ic_content_add.png
rename to android/docs/help/android_images/ic_content_add.png
diff --git a/android/docs/help/android_images/ic_timelapse.png b/android/docs/help/android_images/ic_timelapse.png
new file mode 100644
index 00000000000..9f948825b2c
Binary files /dev/null and b/android/docs/help/android_images/ic_timelapse.png differ
diff --git a/android/help/android_images/ic_translate_a.png b/android/docs/help/android_images/ic_translate_a.png
similarity index 100%
rename from android/help/android_images/ic_translate_a.png
rename to android/docs/help/android_images/ic_translate_a.png
diff --git a/android/help/android_images/info-a-gray.png b/android/docs/help/android_images/info-a-gray.png
similarity index 100%
rename from android/help/android_images/info-a-gray.png
rename to android/docs/help/android_images/info-a-gray.png
diff --git a/android/help/android_images/info-a.png b/android/docs/help/android_images/info-a.png
similarity index 100%
rename from android/help/android_images/info-a.png
rename to android/docs/help/android_images/info-a.png
diff --git a/android/help/android_images/keyman-storage-permission-34b.png b/android/docs/help/android_images/keyman-storage-permission-34b.png
similarity index 100%
rename from android/help/android_images/keyman-storage-permission-34b.png
rename to android/docs/help/android_images/keyman-storage-permission-34b.png
diff --git a/android/help/android_images/keyman-storage-permission-ap.png b/android/docs/help/android_images/keyman-storage-permission-ap.png
similarity index 100%
rename from android/help/android_images/keyman-storage-permission-ap.png
rename to android/docs/help/android_images/keyman-storage-permission-ap.png
diff --git a/android/help/android_images/keyman-storage-permission-at.png b/android/docs/help/android_images/keyman-storage-permission-at.png
similarity index 100%
rename from android/help/android_images/keyman-storage-permission-at.png
rename to android/docs/help/android_images/keyman-storage-permission-at.png
diff --git a/android/help/android_images/khmer-downloading-a.png b/android/docs/help/android_images/khmer-downloading-a.png
similarity index 100%
rename from android/help/android_images/khmer-downloading-a.png
rename to android/docs/help/android_images/khmer-downloading-a.png
diff --git a/android/help/android_images/khmer-install-a.png b/android/docs/help/android_images/khmer-install-a.png
similarity index 100%
rename from android/help/android_images/khmer-install-a.png
rename to android/docs/help/android_images/khmer-install-a.png
diff --git a/android/help/android_images/khmer-readme-a.png b/android/docs/help/android_images/khmer-readme-a.png
similarity index 100%
rename from android/help/android_images/khmer-readme-a.png
rename to android/docs/help/android_images/khmer-readme-a.png
diff --git a/android/help/android_images/khmer-search-a.png b/android/docs/help/android_images/khmer-search-a.png
similarity index 100%
rename from android/help/android_images/khmer-search-a.png
rename to android/docs/help/android_images/khmer-search-a.png
diff --git a/android/help/android_images/khmer-settings-ap.png b/android/docs/help/android_images/khmer-settings-ap.png
similarity index 100%
rename from android/help/android_images/khmer-settings-ap.png
rename to android/docs/help/android_images/khmer-settings-ap.png
diff --git a/android/help/android_images/khmer-welcome-a.png b/android/docs/help/android_images/khmer-welcome-a.png
similarity index 100%
rename from android/help/android_images/khmer-welcome-a.png
rename to android/docs/help/android_images/khmer-welcome-a.png
diff --git a/android/docs/help/android_images/longpress-slider.png b/android/docs/help/android_images/longpress-slider.png
new file mode 100644
index 00000000000..f1ab61f7b3c
Binary files /dev/null and b/android/docs/help/android_images/longpress-slider.png differ
diff --git a/android/help/android_images/menu-icon-a.png b/android/docs/help/android_images/menu-icon-a.png
similarity index 100%
rename from android/help/android_images/menu-icon-a.png
rename to android/docs/help/android_images/menu-icon-a.png
diff --git a/android/help/android_images/other-input-methods.png b/android/docs/help/android_images/other-input-methods.png
similarity index 100%
rename from android/help/android_images/other-input-methods.png
rename to android/docs/help/android_images/other-input-methods.png
diff --git a/android/help/android_images/plus-a.png b/android/docs/help/android_images/plus-a.png
similarity index 100%
rename from android/help/android_images/plus-a.png
rename to android/docs/help/android_images/plus-a.png
diff --git a/android/help/android_images/reset-to-default.png b/android/docs/help/android_images/reset-to-default.png
similarity index 100%
rename from android/help/android_images/reset-to-default.png
rename to android/docs/help/android_images/reset-to-default.png
diff --git a/android/help/android_images/return-ap.png b/android/docs/help/android_images/return-ap.png
similarity index 100%
rename from android/help/android_images/return-ap.png
rename to android/docs/help/android_images/return-ap.png
diff --git a/android/help/android_images/return-at.png b/android/docs/help/android_images/return-at.png
similarity index 100%
rename from android/help/android_images/return-at.png
rename to android/docs/help/android_images/return-at.png
diff --git a/android/help/android_images/return.png b/android/docs/help/android_images/return.png
similarity index 100%
rename from android/help/android_images/return.png
rename to android/docs/help/android_images/return.png
diff --git a/android/help/android_images/select_language.png b/android/docs/help/android_images/select_language.png
similarity index 100%
rename from android/help/android_images/select_language.png
rename to android/docs/help/android_images/select_language.png
diff --git a/android/help/android_images/settings-2-installed-languages-ap.png b/android/docs/help/android_images/settings-2-installed-languages-ap.png
similarity index 100%
rename from android/help/android_images/settings-2-installed-languages-ap.png
rename to android/docs/help/android_images/settings-2-installed-languages-ap.png
diff --git a/android/help/android_images/settings-a.png b/android/docs/help/android_images/settings-a.png
similarity index 100%
rename from android/help/android_images/settings-a.png
rename to android/docs/help/android_images/settings-a.png
diff --git a/android/help/android_images/settings-download-dictionary-background-ap.png b/android/docs/help/android_images/settings-download-dictionary-background-ap.png
similarity index 100%
rename from android/help/android_images/settings-download-dictionary-background-ap.png
rename to android/docs/help/android_images/settings-download-dictionary-background-ap.png
diff --git a/android/help/android_images/settings-download-str-dictionary-ap.png b/android/docs/help/android_images/settings-download-str-dictionary-ap.png
similarity index 100%
rename from android/help/android_images/settings-download-str-dictionary-ap.png
rename to android/docs/help/android_images/settings-download-str-dictionary-ap.png
diff --git a/android/help/android_images/settings-khmer-info-ap.png b/android/docs/help/android_images/settings-khmer-info-ap.png
similarity index 100%
rename from android/help/android_images/settings-khmer-info-ap.png
rename to android/docs/help/android_images/settings-khmer-info-ap.png
diff --git a/android/help/android_images/settings-language-ap.png b/android/docs/help/android_images/settings-language-ap.png
similarity index 100%
rename from android/help/android_images/settings-language-ap.png
rename to android/docs/help/android_images/settings-language-ap.png
diff --git a/android/help/android_images/settings-languages-ap.png b/android/docs/help/android_images/settings-languages-ap.png
similarity index 100%
rename from android/help/android_images/settings-languages-ap.png
rename to android/docs/help/android_images/settings-languages-ap.png
diff --git a/android/docs/help/android_images/settings-screen-ap.png b/android/docs/help/android_images/settings-screen-ap.png
new file mode 100644
index 00000000000..f83da380406
Binary files /dev/null and b/android/docs/help/android_images/settings-screen-ap.png differ
diff --git a/android/help/android_images/settings-screen-install-keyboard-dictionary.png b/android/docs/help/android_images/settings-screen-install-keyboard-dictionary.png
similarity index 100%
rename from android/help/android_images/settings-screen-install-keyboard-dictionary.png
rename to android/docs/help/android_images/settings-screen-install-keyboard-dictionary.png
diff --git a/android/help/android_images/settings-str-dictionary-ap.png b/android/docs/help/android_images/settings-str-dictionary-ap.png
similarity index 100%
rename from android/help/android_images/settings-str-dictionary-ap.png
rename to android/docs/help/android_images/settings-str-dictionary-ap.png
diff --git a/android/help/android_images/settings-str-settings-ap.png b/android/docs/help/android_images/settings-str-settings-ap.png
similarity index 100%
rename from android/help/android_images/settings-str-settings-ap.png
rename to android/docs/help/android_images/settings-str-settings-ap.png
diff --git a/android/help/android_images/settings-suggestions-ap.png b/android/docs/help/android_images/settings-suggestions-ap.png
similarity index 100%
rename from android/help/android_images/settings-suggestions-ap.png
rename to android/docs/help/android_images/settings-suggestions-ap.png
diff --git a/android/help/android_images/settings-two-installed-languages-ap.png b/android/docs/help/android_images/settings-two-installed-languages-ap.png
similarity index 100%
rename from android/help/android_images/settings-two-installed-languages-ap.png
rename to android/docs/help/android_images/settings-two-installed-languages-ap.png
diff --git a/android/help/android_images/settings1-ap.png b/android/docs/help/android_images/settings1-ap.png
similarity index 100%
rename from android/help/android_images/settings1-ap.png
rename to android/docs/help/android_images/settings1-ap.png
diff --git a/android/help/android_images/settings1-at.png b/android/docs/help/android_images/settings1-at.png
similarity index 100%
rename from android/help/android_images/settings1-at.png
rename to android/docs/help/android_images/settings1-at.png
diff --git a/android/help/android_images/settings2-ap.png b/android/docs/help/android_images/settings2-ap.png
similarity index 100%
rename from android/help/android_images/settings2-ap.png
rename to android/docs/help/android_images/settings2-ap.png
diff --git a/android/help/android_images/settings2-at.png b/android/docs/help/android_images/settings2-at.png
similarity index 100%
rename from android/help/android_images/settings2-at.png
rename to android/docs/help/android_images/settings2-at.png
diff --git a/android/help/android_images/settings3-ap.png b/android/docs/help/android_images/settings3-ap.png
similarity index 100%
rename from android/help/android_images/settings3-ap.png
rename to android/docs/help/android_images/settings3-ap.png
diff --git a/android/help/android_images/settings3-at.png b/android/docs/help/android_images/settings3-at.png
similarity index 100%
rename from android/help/android_images/settings3-at.png
rename to android/docs/help/android_images/settings3-at.png
diff --git a/android/help/android_images/settings4-ap.png b/android/docs/help/android_images/settings4-ap.png
similarity index 100%
rename from android/help/android_images/settings4-ap.png
rename to android/docs/help/android_images/settings4-ap.png
diff --git a/android/help/android_images/settings4-at.png b/android/docs/help/android_images/settings4-at.png
similarity index 100%
rename from android/help/android_images/settings4-at.png
rename to android/docs/help/android_images/settings4-at.png
diff --git a/android/help/android_images/settings5-ap.png b/android/docs/help/android_images/settings5-ap.png
similarity index 100%
rename from android/help/android_images/settings5-ap.png
rename to android/docs/help/android_images/settings5-ap.png
diff --git a/android/help/android_images/settings5-at.png b/android/docs/help/android_images/settings5-at.png
similarity index 100%
rename from android/help/android_images/settings5-at.png
rename to android/docs/help/android_images/settings5-at.png
diff --git a/android/help/android_images/settings6-ap.png b/android/docs/help/android_images/settings6-ap.png
similarity index 100%
rename from android/help/android_images/settings6-ap.png
rename to android/docs/help/android_images/settings6-ap.png
diff --git a/android/help/android_images/settings6-at.png b/android/docs/help/android_images/settings6-at.png
similarity index 100%
rename from android/help/android_images/settings6-at.png
rename to android/docs/help/android_images/settings6-at.png
diff --git a/android/help/android_images/settings7-ap.png b/android/docs/help/android_images/settings7-ap.png
similarity index 100%
rename from android/help/android_images/settings7-ap.png
rename to android/docs/help/android_images/settings7-ap.png
diff --git a/android/help/android_images/settings7-at.png b/android/docs/help/android_images/settings7-at.png
similarity index 100%
rename from android/help/android_images/settings7-at.png
rename to android/docs/help/android_images/settings7-at.png
diff --git a/android/help/android_images/share-a.png b/android/docs/help/android_images/share-a.png
similarity index 100%
rename from android/help/android_images/share-a.png
rename to android/docs/help/android_images/share-a.png
diff --git a/android/help/android_images/shift-ap.png b/android/docs/help/android_images/shift-ap.png
similarity index 100%
rename from android/help/android_images/shift-ap.png
rename to android/docs/help/android_images/shift-ap.png
diff --git a/android/help/android_images/shift-at.png b/android/docs/help/android_images/shift-at.png
similarity index 100%
rename from android/help/android_images/shift-at.png
rename to android/docs/help/android_images/shift-at.png
diff --git a/android/help/android_images/spacebar-caption-ap.png b/android/docs/help/android_images/spacebar-caption-ap.png
similarity index 100%
rename from android/help/android_images/spacebar-caption-ap.png
rename to android/docs/help/android_images/spacebar-caption-ap.png
diff --git a/android/help/android_images/themed-banner.png b/android/docs/help/android_images/themed-banner.png
similarity index 100%
rename from android/help/android_images/themed-banner.png
rename to android/docs/help/android_images/themed-banner.png
diff --git a/android/help/android_images/touch-hold-ap.png b/android/docs/help/android_images/touch-hold-ap.png
similarity index 100%
rename from android/help/android_images/touch-hold-ap.png
rename to android/docs/help/android_images/touch-hold-ap.png
diff --git a/android/help/android_images/touch-hold-at.png b/android/docs/help/android_images/touch-hold-at.png
similarity index 100%
rename from android/help/android_images/touch-hold-at.png
rename to android/docs/help/android_images/touch-hold-at.png
diff --git a/android/help/android_images/uninstall-dictionary-notification-ap.png b/android/docs/help/android_images/uninstall-dictionary-notification-ap.png
similarity index 100%
rename from android/help/android_images/uninstall-dictionary-notification-ap.png
rename to android/docs/help/android_images/uninstall-dictionary-notification-ap.png
diff --git a/android/help/android_images/uninstall-notification-ap.png b/android/docs/help/android_images/uninstall-notification-ap.png
similarity index 100%
rename from android/help/android_images/uninstall-notification-ap.png
rename to android/docs/help/android_images/uninstall-notification-ap.png
diff --git a/android/help/android_images/uninstall-notification-at.png b/android/docs/help/android_images/uninstall-notification-at.png
similarity index 100%
rename from android/help/android_images/uninstall-notification-at.png
rename to android/docs/help/android_images/uninstall-notification-at.png
diff --git a/android/help/android_images/video.png b/android/docs/help/android_images/video.png
similarity index 100%
rename from android/help/android_images/video.png
rename to android/docs/help/android_images/video.png
diff --git a/android/help/basic/config/adjust-keyboard-height.md b/android/docs/help/basic/config/adjust-keyboard-height.md
similarity index 100%
rename from android/help/basic/config/adjust-keyboard-height.md
rename to android/docs/help/basic/config/adjust-keyboard-height.md
diff --git a/android/docs/help/basic/config/adjust-longpress-delay.md b/android/docs/help/basic/config/adjust-longpress-delay.md
new file mode 100644
index 00000000000..0f3e740bdfc
--- /dev/null
+++ b/android/docs/help/basic/config/adjust-longpress-delay.md
@@ -0,0 +1,13 @@
+---
+title: Adjust Longpress Delay - Keyman for Android Help
+---
+
+## Adjust Longpress Delay
+
+This menu is used to adjust the duration for triggering a longpress gesture.
+
+Drag the slider to set the delay between 0.3 seconds to 1.5 seconds.
+
+The default value is 0.5 seconds.
+
+![](../../android_images/longpress-slider.png)
diff --git a/android/help/basic/config/index.md b/android/docs/help/basic/config/index.md
similarity index 90%
rename from android/help/basic/config/index.md
rename to android/docs/help/basic/config/index.md
index 1c45d27508e..96a3fb5e35c 100644
--- a/android/help/basic/config/index.md
+++ b/android/docs/help/basic/config/index.md
@@ -44,17 +44,17 @@ Click on this to bring up the Android system configuration for setting Keyman as
Click on this to [adjust the keyboard height](adjust-keyboard-height).
+## Adjust longpress delay
+![](../../android_images/ic_timelapse.png)
+
+Click on this to [adjust the delay time](adjust-longpress-delay) for a longpress gesture.
+
## Spacebar Caption
Click on this to [change the displayed keyboard name](spacebar-caption) on the spacebar.
## Vibrate When Typing
When enabled, the Keyman keyboard will provide haptic feedback (vibrate) when the user presses a key and generates output. The default preference is off.
-## Always show banner
-This toggle is reserved for future features. When off, the text suggestion banner is only displayed when
-the dictionary is enabled. The language associated with the keyboard must match the language associated with
-the dictionary.
-
## Show "Get Started" on startup
When enabled, the Keyman app will display the 'Get Started' screen on app startup.
diff --git a/android/help/basic/config/install-keyboard-or-dictionary.md b/android/docs/help/basic/config/install-keyboard-or-dictionary.md
similarity index 100%
rename from android/help/basic/config/install-keyboard-or-dictionary.md
rename to android/docs/help/basic/config/install-keyboard-or-dictionary.md
diff --git a/android/help/basic/config/spacebar-caption.md b/android/docs/help/basic/config/spacebar-caption.md
similarity index 100%
rename from android/help/basic/config/spacebar-caption.md
rename to android/docs/help/basic/config/spacebar-caption.md
diff --git a/android/help/basic/index.md b/android/docs/help/basic/index.md
similarity index 100%
rename from android/help/basic/index.md
rename to android/docs/help/basic/index.md
diff --git a/android/help/basic/installing-custom-packages.md b/android/docs/help/basic/installing-custom-packages.md
similarity index 100%
rename from android/help/basic/installing-custom-packages.md
rename to android/docs/help/basic/installing-custom-packages.md
diff --git a/android/help/basic/installing-dictionaries.md b/android/docs/help/basic/installing-dictionaries.md
similarity index 100%
rename from android/help/basic/installing-dictionaries.md
rename to android/docs/help/basic/installing-dictionaries.md
diff --git a/android/help/basic/switching-between-keyboards.md b/android/docs/help/basic/switching-between-keyboards.md
similarity index 100%
rename from android/help/basic/switching-between-keyboards.md
rename to android/docs/help/basic/switching-between-keyboards.md
diff --git a/android/help/basic/uninstalling-dictionaries.md b/android/docs/help/basic/uninstalling-dictionaries.md
similarity index 100%
rename from android/help/basic/uninstalling-dictionaries.md
rename to android/docs/help/basic/uninstalling-dictionaries.md
diff --git a/android/help/basic/uninstalling-keyboards.md b/android/docs/help/basic/uninstalling-keyboards.md
similarity index 100%
rename from android/help/basic/uninstalling-keyboards.md
rename to android/docs/help/basic/uninstalling-keyboards.md
diff --git a/android/help/basic/using-the-banner.md b/android/docs/help/basic/using-the-banner.md
similarity index 100%
rename from android/help/basic/using-the-banner.md
rename to android/docs/help/basic/using-the-banner.md
diff --git a/android/help/context/gestures.md b/android/docs/help/context/gestures.md
similarity index 100%
rename from android/help/context/gestures.md
rename to android/docs/help/context/gestures.md
diff --git a/android/help/context/index.md b/android/docs/help/context/index.md
similarity index 100%
rename from android/help/context/index.md
rename to android/docs/help/context/index.md
diff --git a/android/help/context/menu-phone.md b/android/docs/help/context/menu-phone.md
similarity index 100%
rename from android/help/context/menu-phone.md
rename to android/docs/help/context/menu-phone.md
diff --git a/android/help/context/menu-tablet.md b/android/docs/help/context/menu-tablet.md
similarity index 100%
rename from android/help/context/menu-tablet.md
rename to android/docs/help/context/menu-tablet.md
diff --git a/android/help/index.md b/android/docs/help/index.md
similarity index 100%
rename from android/help/index.md
rename to android/docs/help/index.md
diff --git a/android/help/start/enabling-system-keyboard.md b/android/docs/help/start/enabling-system-keyboard.md
similarity index 100%
rename from android/help/start/enabling-system-keyboard.md
rename to android/docs/help/start/enabling-system-keyboard.md
diff --git a/android/help/start/index.md b/android/docs/help/start/index.md
similarity index 100%
rename from android/help/start/index.md
rename to android/docs/help/start/index.md
diff --git a/android/help/start/installing-keyboards.md b/android/docs/help/start/installing-keyboards.md
similarity index 100%
rename from android/help/start/installing-keyboards.md
rename to android/docs/help/start/installing-keyboards.md
diff --git a/android/help/troubleshooting/grant-storage-permission.md b/android/docs/help/troubleshooting/grant-storage-permission.md
similarity index 100%
rename from android/help/troubleshooting/grant-storage-permission.md
rename to android/docs/help/troubleshooting/grant-storage-permission.md
diff --git a/android/help/troubleshooting/index.md b/android/docs/help/troubleshooting/index.md
similarity index 100%
rename from android/help/troubleshooting/index.md
rename to android/docs/help/troubleshooting/index.md
diff --git a/android/help/troubleshooting/integrating.md b/android/docs/help/troubleshooting/integrating.md
similarity index 100%
rename from android/help/troubleshooting/integrating.md
rename to android/docs/help/troubleshooting/integrating.md
diff --git a/android/help/troubleshooting/keyboard-help.md b/android/docs/help/troubleshooting/keyboard-help.md
similarity index 100%
rename from android/help/troubleshooting/keyboard-help.md
rename to android/docs/help/troubleshooting/keyboard-help.md
diff --git a/android/help/about/whatsnew.md b/android/help/about/whatsnew.md
deleted file mode 100644
index 96bd458a0c0..00000000000
--- a/android/help/about/whatsnew.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: What's New
----
-
-Here are some of the new features we have added to Keyman 18.0 for Android:
diff --git a/android/help/android_images/settings-screen-ap.png b/android/help/android_images/settings-screen-ap.png
deleted file mode 100644
index ec85e24e81e..00000000000
Binary files a/android/help/android_images/settings-screen-ap.png and /dev/null differ
diff --git a/common/cpp/km_u16.cpp b/common/cpp/km_u16.cpp
index 84a6c4a75e7..78077202fc4 100644
--- a/common/cpp/km_u16.cpp
+++ b/common/cpp/km_u16.cpp
@@ -5,6 +5,7 @@
*/
#include
+#include
#include "utfcodec.hpp"
#include "km_u16.h"
@@ -387,3 +388,43 @@ double u16tof(KMX_WCHAR* str16) {
std::string str = string_from_u16string(str16);
return strtof(str.c_str(), &pEnd);
}
+
+/**
+ * @brief Trim whitespace from the start (left) of a string
+ * @param p Pointer to u16string
+ * @return Pointer to the string modified to remove leading whitespace
+ */
+KMX_WCHAR* u16ltrim(KMX_WCHAR* p) {
+ if (p && (u16len(p) > 0)) {
+ PKMX_WCHAR q = p;
+ while(iswspace(*q)) q++;
+ u16cpy(p, q);
+ }
+ return p;
+}
+
+/**
+ * @brief Trim whitespace from the end (right) of a string
+ * @param p Pointer to u16string
+ * @return Pointer to the string modified to remove trailing whitespace
+ */
+KMX_WCHAR* u16rtrim(KMX_WCHAR *p) {
+ if (p && (u16len(p) > 0)) {
+ PKMX_WCHAR q = p + u16len(p) - 1;
+ if (iswspace(*q)) {
+ while (iswspace(*q) && q > p) q--;
+ if (!iswspace(*q)) q++;
+ *q = '\0'; // delete first following whitespace
+ }
+ }
+ return p;
+}
+
+/**
+ * @brief Trim whitespace from both the start and end of a string
+ * @param p Pointer to u16string
+ * @return Pointer to the string modified to remove leading and trailing whitespace
+ */
+KMX_WCHAR* u16trim(KMX_WCHAR *p) {
+ return u16rtrim(u16ltrim(p));
+}
\ No newline at end of file
diff --git a/common/include/km_u16.h b/common/include/km_u16.h
index f0c2c2d16c1..91df6b0eb92 100644
--- a/common/include/km_u16.h
+++ b/common/include/km_u16.h
@@ -74,3 +74,12 @@ KMX_CHAR* strrchr_slash(KMX_CHAR* Name);
const KMX_WCHAR* u16rchr_slash(KMX_WCHAR const* Name);
std::string toHex(int num1);
+
+/** @brief Trim whitespace from the start (left) of a string */
+KMX_WCHAR* u16ltrim(KMX_WCHAR* p);
+
+/** @brief Trim whitespace from the end (right) of a string */
+KMX_WCHAR* u16rtrim(KMX_WCHAR *p);
+
+/** @brief Trim whitespace from both the start and end of a string */
+KMX_WCHAR* u16trim(KMX_WCHAR *p);
diff --git a/common/models/tsconfig.kmw-worker-base.json b/common/models/tsconfig.kmw-worker-base.json
deleted file mode 100644
index 0b581af1be3..00000000000
--- a/common/models/tsconfig.kmw-worker-base.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "extends": "../../web/tsconfig.base.json",
-
- "compilerOptions": {
- // To help better support legacy Android devices
- "downlevelIteration": true,
- // Facilitates & simplifies stitching together the worker sourcemaps during the polyfill-concatenation step.
- "inlineSourceMap": true,
- // May not be set at the same time as the prior setting.
- "sourceMap": false
- }
-}
diff --git a/common/models/types/.build-builder b/common/models/types/.build-builder
deleted file mode 100644
index 6db097fddbb..00000000000
--- a/common/models/types/.build-builder
+++ /dev/null
@@ -1 +0,0 @@
-The presence of this file tells CI to use the new builder_ style parameters for build.sh.
\ No newline at end of file
diff --git a/common/models/types/.gitignore b/common/models/types/.gitignore
deleted file mode 100644
index e113ad0abd6..00000000000
--- a/common/models/types/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-# Specifically here:
-test.js
-
-node_modules/
-build/
diff --git a/common/models/types/README.md b/common/models/types/README.md
deleted file mode 100644
index 3d7dbf7d595..00000000000
--- a/common/models/types/README.md
+++ /dev/null
@@ -1,68 +0,0 @@
-`@keymanapp/models-types`
-=========================
-
-Declares TypeScript types and interfaces required to define **Lexical
-Models**, **Word breaking functions**, and all things in between.
-
-Install
--------
-
-> (see below if you are working in the keymanapp/keyman repo!)
-
- npm install --save-dev @keymanapp/models-types
-
-Usage
------
-
-Say you are creating a **custom lexical model**. Then import this module
-in your lexical model file!
-
-```Typescript
-// my-nifty-model.ts
-
-import "@keymanapp/models-types";
-
-export class MyNiftyClass implements LexicalModel {
- configure(capabilities: Capabilities): Configuration {
- // TODO: implement me!
- throw new Error("Method not implemented.");
- }
- predict(transform: Transform, context: Context): ProbabilityMass[] {
- // TODO: implement me!
- throw new Error("Method not implemented.");
- }
- wordbreak(context: Context): string {
- // TODO: implement me!
- throw new Error("Method not implemented.");
- }
- punctuation?: LexicalModelPunctuation;
-}
-```
-
-If your are creating a **custom word breaker**, you would do the same:
-
-```typescript
-import "@keymanapp/models-types";
-
-export let myShinyWordBreaker: WordBreakingFunction;
-myShinyWordBreaker = function (phrase: string): Span[] {
- // TODO: implement me!
- throw new Error("Function not implemented")
-}
-```
-
-Look at [index.d.ts](./index.d.ts) for documentation on each and every
-type!
-
-
-Usage within keymanapp/keyman repo
-----------------------------------
-
-To use it in other subprojects within keymanapp/keyman, ensure that this package
-is linked using `"*"`. Add this dependency to your subproject like so:
-
-```json
- "dependencies": {
- "@keymanapp/models-types": "*",
- }
-```
diff --git a/common/models/types/build.sh b/common/models/types/build.sh
deleted file mode 100755
index 25bf4515c43..00000000000
--- a/common/models/types/build.sh
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/usr/bin/env bash
-#
-# Packages the @keymanapp/models-types package.
-#
-## START STANDARD BUILD SCRIPT INCLUDE
-# adjust relative paths as necessary
-THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
-. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh"
-## END STANDARD BUILD SCRIPT INCLUDE
-
-. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
-. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
-
-builder_describe "Keyman model types package" \
- "clean" \
- "configure" \
- "build" \
- "test" \
- "publish publish to npm" \
- "--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \
- "--dry-run,-n don't actually publish, just dry run"
-
-builder_describe_outputs \
- configure /node_modules \
- build /common/models/types/build/common/models/types/tsconfig.tsbuildinfo
-
-builder_parse "$@"
-
-#-------------------------------------------------------------------------------------------------------------------
-
-builder_run_action clean rm -rf ./build/
-builder_run_action configure verify_npm_setup
-builder_run_action build tsc --build
-builder_run_action test tsc test.ts -lib es6
-builder_run_action publish builder_publish_npm
diff --git a/common/models/types/package.json b/common/models/types/package.json
deleted file mode 100644
index a9f68f4ea1e..00000000000
--- a/common/models/types/package.json
+++ /dev/null
@@ -1,38 +0,0 @@
-{
- "name": "@keymanapp/models-types",
- "description": "Type definitions in used in the modeling (lexical model/predictive text) component of Keyman.",
- "types": "./index.d.ts",
- "scripts": {
- "test": "tsc test.ts -lib es6"
- },
- "repository": {
- "type": "git",
- "url": "https://github.com/keymanapp/keyman"
- },
- "keywords": [
- "lmlayer",
- "keyman",
- "predictive",
- "text",
- "types",
- "typing",
- "typescript"
- ],
- "author": "Marc Durdin (https://github.com/mcdurdin)",
- "contributors": [
- "Eddie Antonio Santos (https://github.com/eddieantonio)",
- "Joshua Horton"
- ],
- "license": "MIT",
- "bugs": {
- "url": "https://github.com/keymanapp/keyman/issues"
- },
- "homepage": "https://github.com/keymanapp/keyman/tree/master/common/models/types#readme",
- "devDependencies": {
- "typescript": "^5.4.5"
- },
- "files": [
- "README.md",
- "index.d.ts"
- ]
-}
diff --git a/common/models/types/tsconfig.json b/common/models/types/tsconfig.json
deleted file mode 100644
index ddd1c5b196a..00000000000
--- a/common/models/types/tsconfig.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "extends": "../tsconfig.kmw-worker-base.json",
- "compilerOptions": {
- "declaration": true,
- "outDir": "build/",
- },
- "include": ["./*.ts"],
- "exclude": ["test.ts"]
-}
diff --git a/common/web/eslint/eslintNoNodeImports.js b/common/tools/eslint/eslintNoNodeImports.js
similarity index 100%
rename from common/web/eslint/eslintNoNodeImports.js
rename to common/tools/eslint/eslintNoNodeImports.js
diff --git a/common/tools/hextobin/build.sh b/common/tools/hextobin/build.sh
index c1e2338e9d8..073b008bf2e 100755
--- a/common/tools/hextobin/build.sh
+++ b/common/tools/hextobin/build.sh
@@ -11,7 +11,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
builder_describe "Build hextobin" clean configure build
builder_describe_outputs \
- configure /node_modules \
+ configure /common/tools/hextobin/.configured \
build /common/tools/hextobin/build/index.js
builder_parse "$@"
diff --git a/common/tools/hextobin/package.json b/common/tools/hextobin/package.json
index fb651481f7a..708dace5028 100644
--- a/common/tools/hextobin/package.json
+++ b/common/tools/hextobin/package.json
@@ -15,5 +15,8 @@
"main": "build/index.js",
"bin": {
"hextobin": "build/hextobin.js"
+ },
+ "scripts": {
+ "postinstall": "echo configured > .configured"
}
}
diff --git a/common/web/build.sh b/common/web/build.sh
index fbc015613fd..798db3b231f 100755
--- a/common/web/build.sh
+++ b/common/web/build.sh
@@ -8,15 +8,9 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
. "${THIS_SCRIPT%/*}/../../resources/build/builder.inc.sh"
## END STANDARD BUILD SCRIPT INCLUDE
-#
-# TODO: future modules may include
-# :sentry-manager \
-#
-
builder_describe "Keyman common web modules" \
:keyman-version \
:types \
- :utils \
clean \
configure \
build \
diff --git a/common/web/tsconfig.kmw-main-base.json b/common/web/tsconfig.kmw-main-base.json
deleted file mode 100644
index 0ed8703e5e2..00000000000
--- a/common/web/tsconfig.kmw-main-base.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "../../web/tsconfig.base.json"
-}
diff --git a/common/web/types/.eslintrc.cjs b/common/web/types/.eslintrc.cjs
index 98db7f812e5..b28ba0941b1 100644
--- a/common/web/types/.eslintrc.cjs
+++ b/common/web/types/.eslintrc.cjs
@@ -14,7 +14,7 @@ module.exports = {
overrides: [
{
files: "src/**/*.ts",
- extends: ["../../../common/web/eslint/eslintNoNodeImports.js"],
+ extends: ["../../../common/tools/eslint/eslintNoNodeImports.js"],
},
],
rules: {
diff --git a/common/web/types/package.json b/common/web/types/package.json
index 20f84bc1409..1ba487aafdf 100644
--- a/common/web/types/package.json
+++ b/common/web/types/package.json
@@ -8,9 +8,11 @@
"unicode"
],
"type": "module",
+ "types": "./build/src/main.d.ts",
"exports": {
".": {
"es6-bundling": "./src/main.ts",
+ "types": "./build/src/main.d.ts",
"default": "./build/src/main.js"
}
},
@@ -68,6 +70,7 @@
"src/kmx/kmx-plus.ts",
"src/kmx/kmx-builder.ts",
"src/kmx/element-string.ts",
+ "src/lexical-model-types.ts",
"src/kmx/string-list.ts",
"src/ldml-keyboard/ldml-keyboard-testdata-xml.ts",
"src/ldml-keyboard/unicodeset-parser-api.ts",
diff --git a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts
index 786d9213e2b..e24fd295c84 100644
--- a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts
+++ b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts
@@ -7,6 +7,11 @@
// writing
//
+/**
+ * On screen keyboard description consisting of specific layouts for tablet, phone,
+ * and desktop. Despite its name, this format is used for both touch layouts and
+ * hardware-style layouts.
+ */
export interface TouchLayoutFile {
tablet?: TouchLayoutPlatform;
phone?: TouchLayoutPlatform;
@@ -17,6 +22,7 @@ export type TouchLayoutFont = string;
export type TouchLayoutFontSize = string;
export type TouchLayoutDefaultHint = "none"|"dot"|"longpress"|"multitap"|"flick"|"flick-n"|"flick-ne"|"flick-e"|"flick-se"|"flick-s"|"flick-sw"|"flick-w"|"flick-nw";
+/** touch layout specification for a specific platform like phone or tablet */
export interface TouchLayoutPlatform {
font?: TouchLayoutFont;
fontsize?: TouchLayoutFontSize;
@@ -27,6 +33,7 @@ export interface TouchLayoutPlatform {
export type TouchLayoutLayerId = string; // pattern = /^[a-zA-Z0-9_-]+$/
+/** a layer with rows of keys on a touch layout */
export interface TouchLayoutLayer {
id: TouchLayoutLayerId;
row: TouchLayoutRow[];
@@ -34,6 +41,7 @@ export interface TouchLayoutLayer {
export type TouchLayoutRowId = number;
+/** a row of keys on a touch layout */
export interface TouchLayoutRow {
id: TouchLayoutRowId;
key: TouchLayoutKey[];
@@ -62,22 +70,40 @@ export const PRIVATE_USE_IDS = [
*
* Make sure that when one is updated, the other also is. TS types are compile-time only,
* so the run-time-accessible mapping in activeLayout.ts cannot be auto-generated by TS. */
+/** defines a key on a touch layout */
export interface TouchLayoutKey {
+ /** key id: used to find key in VKDictionary, or a standard key from the K_ enumeration */
id?: TouchLayoutKeyId;
+ /** text to display on key cap */
text?: string;
+ /**
+ * the modifier combination (not layer) that should be used in key events,
+ * for this key, overriding the layer that the key is a part of.
+ */
layer?: TouchLayoutLayerId;
+ /** the next layer to switch to after this key is pressed */
nextlayer?: TouchLayoutLayerId;
+ /** font */
font?: TouchLayoutFont;
+ /** fontsize */
fontsize?: TouchLayoutFontSize;
+ /** the type of key */
sp?: TouchLayoutKeySp;
+ /** padding */
pad?: TouchLayoutKeyPad;
+ /** width of the key */
width?: TouchLayoutKeyWidth;
+ /** longpress keys, also known as subkeys */
sk?: TouchLayoutSubKey[];
+ /** flicks */
flick?: TouchLayoutFlick;
+ /** multitaps */
multitap?: TouchLayoutSubKey[];
+ /** hint e.g. for longpress */
hint?: string;
};
+/** key type like regular key, framekeys, deadkeys, blank, etc. */
export const enum TouchLayoutKeySp {
normal=0,
/** A 'frame' key, such as Shift or Enter, which is styled accordingly; uses
@@ -102,29 +128,54 @@ export const enum TouchLayoutKeySp {
spacer=10
};
+/** padding for a key */
export type TouchLayoutKeyPad = number; // 0-100000
+/** width of a key */
export type TouchLayoutKeyWidth = number; // 0-100000
+/** defines a subkey */
export interface TouchLayoutSubKey {
+ /** key id: used to find key in VKDictionary, or a standard key from the K_ enumeration */
id: TouchLayoutKeyId;
+ /** text to display on key cap */
text?: string;
+ /**
+ * the modifier combination (not layer) that should be used in key events,
+ * for this key, overriding the layer that the key is a part of.
+ */
layer?: TouchLayoutLayerId;
+ /** the next layer to switch to after this key is pressed */
nextlayer?: TouchLayoutLayerId;
+ /** font */
font?: TouchLayoutFont;
+ /** fontsize */
fontsize?: TouchLayoutFontSize;
+ /** the type of key */
sp?: TouchLayoutKeySp;
+ /** padding */
pad?: TouchLayoutKeyPad;
+ /** width of the key */
width?: TouchLayoutKeyWidth;
+ /** use this subkey if no other selected */
default?: boolean; // Only used for longpress currently
};
+/** defines all possible flicks for a key */
export interface TouchLayoutFlick {
+ /** flick up (north) */
n?: TouchLayoutSubKey;
+ /** flick down (south) */
s?: TouchLayoutSubKey;
+ /** flick right (east) */
e?: TouchLayoutSubKey;
+ /** flick left (west) */
w?: TouchLayoutSubKey;
+ /** flick up-right (north-east) */
ne?: TouchLayoutSubKey;
+ /** flick up-left (north-west) */
nw?: TouchLayoutSubKey;
+ /** flick down-right (south-east) */
se?: TouchLayoutSubKey;
+ /** flick down-left (south-west) */
sw?: TouchLayoutSubKey;
};
diff --git a/common/web/types/src/kmx/kmx-plus/element-string.ts b/common/web/types/src/kmx/kmx-plus/element-string.ts
index c7597b4f6ba..b389107ccee 100644
--- a/common/web/types/src/kmx/kmx-plus/element-string.ts
+++ b/common/web/types/src/kmx/kmx-plus/element-string.ts
@@ -1,7 +1,7 @@
import { constants } from '@keymanapp/ldml-keyboard-constants';
import { DependencySections, StrsItem, UsetItem } from './kmx-plus.js';
import { ElementParser, ElementSegment, ElementType } from '../../ldml-keyboard/pattern-parser.js';
-import { util } from '@keymanapp/common-types';
+import * as util from '../../util/util.js';
import MATCH_HEX_ESCAPE = util.MATCH_HEX_ESCAPE;
import unescapeOneQuadString = util.unescapeOneQuadString;
diff --git a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts
index 1d19cdd4d01..0cb49e527c7 100644
--- a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts
+++ b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts
@@ -2,8 +2,8 @@ import { constants } from '@keymanapp/ldml-keyboard-constants';
import * as r from 'restructure';
import { ElementString } from './element-string.js';
import { ListItem } from '../../ldml-keyboard/string-list.js';
-import { util } from '@keymanapp/common-types';
-import { KMX } from '@keymanapp/common-types';
+import * as util from '../../util/util.js';
+import * as KMX from '../kmx.js';
import { UnicodeSetParser, UnicodeSet } from '../../ldml-keyboard/unicodeset-parser-api.js';
import { VariableParser } from '../../ldml-keyboard/pattern-parser.js';
import { MarkerParser } from '../../ldml-keyboard/pattern-parser.js';
@@ -291,7 +291,7 @@ export class Vars extends Section {
});
}
findStringVariableValue(id: string): string {
- return Vars.findVariable(this.strings, id)?.value?.value; // Unwrap: Variable, StrsItem
+ return Vars.findVariable(this.strings, id)?.value?.value ?? null; // Unwrap: Variable, StrsItem
}
substituteSetRegex(str: string, sections: DependencySections): string {
return str.replaceAll(VariableParser.SET_REFERENCE, (_entire, id) => {
diff --git a/common/models/types/index.d.ts b/common/web/types/src/lexical-model-types.ts
similarity index 96%
rename from common/models/types/index.d.ts
rename to common/web/types/src/lexical-model-types.ts
index 0d6223bc64a..7c5cfdf643f 100644
--- a/common/models/types/index.d.ts
+++ b/common/web/types/src/lexical-model-types.ts
@@ -15,14 +15,14 @@
*
* See also: https://developer.mozilla.org/en-US/docs/Web/API/USVString
*/
-declare type USVString = string;
+export type USVString = string;
-declare type CasingForm = 'lower' | 'initial' | 'upper';
+export type CasingForm = 'lower' | 'initial' | 'upper';
/**
* Represents one lexical entry and its probability..
*/
-type TextWithProbability = {
+export type TextWithProbability = {
/**
* A lexical entry (word) offered by the model.
*
@@ -42,7 +42,7 @@ type TextWithProbability = {
* Used to facilitate edit-distance calculations by allowing the LMLayer to
* efficiently search the model's lexicon in a Trie-like manner.
*/
-declare interface LexiconTraversal {
+export interface LexiconTraversal {
/**
* Provides an iterable pattern used to search for words with a 'keyed' prefix matching
* the current traversal state's prefix when a new character is appended. Iterating
@@ -116,7 +116,7 @@ declare interface LexiconTraversal {
/**
* The model implementation, within the Worker.
*/
-declare interface LexicalModel {
+export interface LexicalModel {
/**
* Processes `config` messages, configuring the newly-loaded model based on the host
* platform's capability restrictions.
@@ -263,7 +263,7 @@ declare interface LexicalModel {
* first, you delete the specified amount amount from the left
* and right, then you insert the provided text.
*/
-declare interface Transform {
+export interface Transform {
/**
* Facilitates use of unique identifiers for tracking the Transform and
* any related data from its original source, as the reference cannot be
@@ -298,7 +298,7 @@ declare interface Transform {
/**
* A concrete suggestion
*/
-declare interface Suggestion {
+export interface Suggestion {
/**
* Indicates the externally-supplied id of the Transform that prompted
* the Suggestion. Automatically handled by the LMLayer; models should
@@ -341,11 +341,11 @@ declare interface Suggestion {
autoAccept?: boolean
}
-interface Reversion extends Suggestion {
+export interface Reversion extends Suggestion {
tag: 'revert';
}
-interface Keep extends Suggestion {
+export interface Keep extends Suggestion {
tag: 'keep';
/**
@@ -368,12 +368,12 @@ interface Keep extends Suggestion {
*
* If left undefined, the consumers will assume this is a prediction.
*/
-type SuggestionTag = undefined | 'keep' | 'revert' | 'correction' | 'emoji';
+export type SuggestionTag = undefined | 'keep' | 'revert' | 'correction' | 'emoji';
/**
* The text and environment surrounding the insertion point (text cursor).
*/
-declare interface Context {
+export interface Context {
/**
* Up to maxLeftContextCodeUnits code units of Unicode scalar value
* (i. e., characters) to the left of the insertion point in the
@@ -413,7 +413,7 @@ declare interface Context {
* from ambiguous text sequences. Designed for use with fat-finger correction
* and similar typing ambiguities.
*/
-interface ProbabilityMass {
+export interface ProbabilityMass {
/**
* An individual sample from a Distribution over the same type.
*/
@@ -426,12 +426,12 @@ interface ProbabilityMass {
p: number;
}
-declare type Distribution = ProbabilityMass[];
+export type Distribution = ProbabilityMass[];
/**
* A type augmented with an optional probability.
*/
-type Outcome = T & {
+export type Outcome = T & {
/**
* [optional] probability of this outcome.
*/
@@ -441,7 +441,7 @@ type Outcome = T & {
/**
* A type augmented with a probability.
*/
-type WithOutcome = T & {
+export type WithOutcome = T & {
/**
* Probability of this outcome.
*/
@@ -458,7 +458,7 @@ type WithOutcome = T & {
* prediction, as well as what operations the keyboard is allowed to do on the
* underlying buffer.
*/
-declare interface Capabilities {
+export interface Capabilities {
/**
* The maximum amount of UTF-16 code points that the keyboard will provide to
* the left of the cursor, as an integer.
@@ -482,7 +482,7 @@ declare interface Capabilities {
/**
* Configuration of the LMLayer, sent back to the keyboard.
*/
-declare interface Configuration {
+export interface Configuration {
/**
* How many UTF-16 code units maximum to send as the context to the
* left of the cursor ("left" in the Unicode character stream).
@@ -538,14 +538,14 @@ declare interface Configuration {
* @returns an array of spans from the phrase, in order as they appear in the
* phrase, each span which representing a word.
*/
-declare interface WordBreakingFunction {
+export interface WordBreakingFunction {
// invariant: span[i].end <= span[i + 1].start
// invariant: for all span[i] and span[i + 1], there does not exist a span[k]
// where span[i].end <= span[k].start AND span[k].end <= span[i + 1].start
(phrase: string): Span[];
}
-declare interface CasingFunction {
+export interface CasingFunction {
(caseToApply: CasingForm, text: string, defaultApplyCasing?: CasingFunction): string;
}
@@ -553,7 +553,7 @@ declare interface CasingFunction {
* A span of text in a phrase. This is usually meant to represent words from a
* pharse.
*/
-declare interface Span {
+export interface Span {
// invariant: start < end (empty spans not allowed)
readonly start: number;
// invariant: end > end (empty spans not allowed)
@@ -572,7 +572,7 @@ declare interface Span {
/**
* Options for various punctuation to use in suggestions.
*/
-interface LexicalModelPunctuation {
+export interface LexicalModelPunctuation {
/**
* The quotes that appear in "keep" suggestions, e.g., keep what the user
* typed verbatim.
diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts
index 7e0dc2f7b22..df21b0fed35 100644
--- a/common/web/types/src/main.ts
+++ b/common/web/types/src/main.ts
@@ -29,3 +29,5 @@ export * as KMXPlus from './kmx/kmx-plus/kmx-plus.js';
export { UnicodeSetParser, UnicodeSet } from './ldml-keyboard/unicodeset-parser-api.js';
export { VariableParser, MarkerParser } from './ldml-keyboard/pattern-parser.js';
export { ElementString } from './kmx/kmx-plus/element-string.js';
+
+export { USVString, CasingForm, CasingFunction, TextWithProbability, LexiconTraversal, LexicalModel, LexicalModelPunctuation, Transform, Suggestion, Reversion, Keep, SuggestionTag, Context, Distribution, Outcome, WithOutcome, ProbabilityMass, Configuration, Capabilities, WordBreakingFunction, Span } from './lexical-model-types.js';
diff --git a/common/models/types/test.ts b/common/web/types/test/lexical-model-types.tests.ts
similarity index 54%
rename from common/models/types/test.ts
rename to common/web/types/test/lexical-model-types.tests.ts
index a3607418091..8944342ed6e 100644
--- a/common/models/types/test.ts
+++ b/common/web/types/test/lexical-model-types.tests.ts
@@ -1,12 +1,12 @@
-///
-
/**
* This file "tests" the exports from the main module.
- *
+ *
* Since the exports are all types, the "test" here is that the type can be
* imported and compiled without any compiler errors.
*/
+import { USVString, Transform, Suggestion, SuggestionTag, Context, Capabilities, Configuration, Distribution, WordBreakingFunction, Span, LexicalModelPunctuation, ElementString, KMXPlus } from '@keymanapp/common-types';
+
export let u: USVString;
export let l: Transform
export let s: Suggestion;
@@ -17,4 +17,9 @@ export let conf: Configuration;
export let d: Distribution;
export let wbf: WordBreakingFunction;
export let sp: Span;
-export let lmp: LexicalModelPunctuation;
\ No newline at end of file
+export let lmp: LexicalModelPunctuation;
+
+
+// try some of the other types - that should still work
+export let elemString: ElementString;
+export let section: KMXPlus.Section;
diff --git a/common/web/types/test/tsconfig.json b/common/web/types/test/tsconfig.json
index b034236bd75..9678c49945f 100644
--- a/common/web/types/test/tsconfig.json
+++ b/common/web/types/test/tsconfig.json
@@ -11,11 +11,12 @@
},
"include": [
"**/test-*.ts",
+ "**/*.tests.ts",
"./helpers/*.ts",
],
"references": [
{ "path": "../../keyman-version" },
{ "path": "../../../../core/include/ldml/"},
{ "path": "../" },
- ]
+ ],
}
diff --git a/common/windows/cpp/include/registry.h b/common/windows/cpp/include/registry.h
index 67cdfc2f69e..2627d06522f 100644
--- a/common/windows/cpp/include/registry.h
+++ b/common/windows/cpp/include/registry.h
@@ -110,6 +110,10 @@
#define REGSZ_KeyboardHotkeysAreToggle "hotkeys are toggles"
#define REGSZ_DeadkeyConversionMode "deadkey conversion mode" // CU // I4552
#define REGSZ_ZapVirtualKeyCode "zap virtual key code" // LM, defaults to 0x0E (_VK_PREFIX_DEFAULT)
+/* Default is to only use left modifier in hotkeys trigger */
+#define REGSZ_AllowRightModifierHotKey "allow right modifier for hotkey"
+
+
/*
Debug flags
These are all stored in HKCU\Software\Keyman\Debug
@@ -121,13 +125,7 @@
#define REGSZ_Flag_ShouldSerializeInput "Flag_ShouldSerializeInput"
-/* REGSZ_Keyman_Debug DWORD: Use old non-chiral Win32 API RegisterHotkey instead of left-only hotkeys */
-
-#define REGSZ_Flag_UseRegisterHotkey "Flag_UseRegisterHotkey"
-
-/* REGSZ_Flag_UseCachedHotkeyModifierState DWORD: Use old cached modifier state when checking hotkeys; ignores UseRegisterHotkey if FALSE */
-#define REGSZ_Flag_UseCachedHotkeyModifierState "Flag_UseCachedHotkeyModifierState"
/* DWORD: Enable/disable deep TSF integration, default enabled; 0 = disabled, 1 = enabled, 2 = default */
diff --git a/common/windows/delphi/general/RegistryKeys.pas b/common/windows/delphi/general/RegistryKeys.pas
index 47e7df754a0..e351b3860ff 100644
--- a/common/windows/delphi/general/RegistryKeys.pas
+++ b/common/windows/delphi/general/RegistryKeys.pas
@@ -113,10 +113,11 @@ interface
SRegValue_ShowWelcome = 'show welcome'; // CU
SRegValue_UseAdvancedInstall = 'use advanced install'; // CU
- SRegValue_AltGrCtrlAlt = 'simulate altgr'; // CU
- SRegValue_KeyboardHotKeysAreToggle = 'hotkeys are toggles'; // CU
+ SRegValue_AltGrCtrlAlt = 'simulate altgr'; // CU
+ SRegValue_KeyboardHotKeysAreToggle = 'hotkeys are toggles'; // CU
+ SRegValue_AllowRightModifierHotKey = 'allow right modifier for hotkey'; // CU
SRegValue_ReleaseShiftKeysAfterKeyPress = 'release shift keys after key press'; // CU
- SRegValue_TestKeymanFunctioning = 'test keyman functioning'; // CU, default true
+ SRegValue_TestKeymanFunctioning = 'test keyman functioning'; // CU, default true
SRegValue_CreateStartMenuAsSubfolders = 'create start menu as subfolders'; // CU
SRegValue_CreateUninstallEntries = 'create uninstall entries'; // CU
@@ -379,8 +380,6 @@ interface
SRegKey_KeymanEngineDebug_CU = SRegKey_KeymanEngineRoot_CU + '\Debug';
- SRegValue_Flag_UseRegisterHotkey = 'Flag_UseRegisterHotkey';
- SRegValue_Flag_UseCachedHotkeyModifierState = 'Flag_UseCachedHotkeyModifierState';
SRegValue_Flag_ShouldSerializeInput = 'Flag_ShouldSerializeInput';
SRegValue_Flag_UseAutoStartTask = 'Flag_UseAutoStartTask';
SRegValue_Flag_SyncLanguagesToCloud = 'Flag_SyncLanguagesToCloud';
diff --git a/core/src/layout.hpp b/core/src/layout.hpp
new file mode 100644
index 00000000000..2e3d2833b4a
--- /dev/null
+++ b/core/src/layout.hpp
@@ -0,0 +1,185 @@
+/*
+ * Keyman is copyright (C) SIL International. MIT License.
+ *
+ * Keyman Keyboard Processor API - On-Screen Keyboard Layout Interfaces
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+#include "keyman_core_api.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/**
+ * Possible directions of a flick
+ */
+enum keyboard_layout_flick_direction {
+ /** flick up (north) */
+ n = 0,
+ /** flick down (south) */
+ s = 1,
+ /** flick right (east) */
+ e = 2,
+ /** flick left (west) */
+ w = 3,
+ /** flick up-right (north-east) */
+ ne = 4,
+ /** flick up-left (north-west) */
+ nw = 5,
+ /** flick down-right (south-east) */
+ se = 6,
+ /** flick down-left (south-west) */
+ sw = 7
+};
+
+/**
+ * key type like regular key, framekeys, deadkeys, blank, etc.
+ */
+enum keyboard_layout_key_type {
+ /** regular key */
+ normal = 0,
+ /** A 'frame' key, such as Shift or Enter */
+ special = 1,
+ /** A 'frame' key, such as Shift or Enter, which is is active, such as
+ * the shift key on a shift layer */
+ specialActive = 2,
+ /** **KeymanWeb runtime private use:** a variant of `special` with the
+ * keyboard font rather than 'KeymanwebOsk' font */
+ customSpecial = 3,
+ /** **KeymanWeb runtime private use:** a variant of `specialActive` with the
+ * keyboard font rather than 'KeymanwebOsk' font. */
+ customSpecialActive = 4,
+ /** A deadkey */
+ deadkey = 8,
+ /** A key which is rendered as a blank keycap, should block any interaction */
+ blank = 9,
+ /** Renders the key only as a gap or spacer, should block any interaction */
+ spacer = 10
+};
+
+/**
+ * A key on a touch layout/on-screen keyboard
+ */
+struct keyboard_layout_key {
+ /** key id */
+ std::u16string id; // TODO-WEB-CORE: perhaps necessary for special keys, Enter, etc? or can we get that from virtualKey?
+ /** the virtual key code */
+ int virtualKey; // TODO-WEB-CORE: do we need this? both id and virtualKey? Or just one of them?
+ /** text to display on key cap */
+ std::u16string display;
+ /** hint e.g. for longpress */
+ std::u16string hint;
+ /** the type of key */
+ keyboard_layout_key_type type;
+
+ /**
+ * the modifier combination (not layer) that should be used in key events,
+ * for this key, overriding the layer that the key is a part of.
+ */
+ int modifiersOverride;
+ /** the next layer to switch to after this key is pressed */
+ std::u16string nextLayerId;
+
+ // touch layouts only
+
+ /** padding - space to the left of key (in what units?) */
+ int gap;
+ /** width of the key (in what units?) */
+ int width;
+
+ /** longpress keys, also known as subkeys */
+ std::vector longpresses;
+ /** multitaps */
+ std::vector multiTaps;
+ /** flicks */
+ std::map flicks;
+};
+
+/**
+ * a row of keys on a touch layout/on-screen keyboard
+ */
+struct keyboard_layout_row {
+ /** row id */
+ int id; // TODO-WEB-CORE: do we need this? Web has it (`TouchLayoutRow`)
+ /** keys in this row */
+ std::vector keys;
+};
+
+/**
+ * a layer with rows of keys on a touch layout/on-screen keyboard
+ */
+struct keyboard_layout_layer {
+ /** layer id */
+ std::u16string id;
+ /** layer modifiers */
+ // TODO-WEB-CORE: we added this during our discussion, but Web doesn't have it.
+ // Should be an enum if it's needed.
+ int modifiers; //? 0 = default, n = shift, etc. -1 = unspecified?
+ /** rows in this layer */
+ std::vector rows;
+};
+
+/**
+ * layout specification for a specific platform like desktop, phone or tablet
+ */
+struct keyboard_layout_platform {
+ /** platform form factor, e.g. 'iso', 'touch', 'ansi', ... (see ldml spec) */
+ std::u16string form;
+ /** width of screen for touch layout */
+ int minWidthMm; // we don't have mobile vs tablet, instead use this
+ /** layers for this platform */
+ std::vector layers;
+
+ // TODO-WEB-CORE: Do we need these:
+ // Web additionally has:
+ // - font (should be in CSS; we have it in `keyboard_layout`)
+ // - fontsize (should be in CSS; we have it in `keyboard_layout`)
+ // - displayUnderlying
+ // - defaultHint ("none"|"dot"|"longpress"|"multitap"|"flick"|"flick-n"|"flick-ne"|
+ // "flick-e"|"flick-se"|"flick-s"|"flick-sw"|"flick-w"|"flick-nw")
+};
+
+/**
+ * On screen keyboard description consisting of specific layouts for different
+ * form factors.
+ */
+struct keyboard_layout {
+ /** layouts for different form factors */
+ std::vector platforms;
+ /** font face name to use for key caps*/
+ std::string fontFacename;
+ /** font size to use for key caps */
+ int fontSizeEm; // TODO-WEB-CORE: em? px? something else?
+};
+
+
+/**
+ * Get the on-screen keyboard layout for the specified keyboard.
+ *
+ * @param keyboard [in] The keyboard to get the layout for.
+ * @param layout [out] The on-screen keyboard layout.
+ * @return km_core_status `KM_CORE_STATUS_OK`: On success.
+ * `KM_CORE_STATUS_INVALID_ARGUMENT`: If `keyboard` is not a valid keyboard or `layout` is null.
+ */
+km_core_status
+keyboard_get_layout(
+ km_core_keyboard const* keyboard,
+ keyboard_layout** layout
+);
+
+
+/**
+ * Dispose the on-screen keyboard layout.
+ */
+void
+keyboard_layout_dispose(keyboard_layout* layout);
+
+#if defined(__cplusplus)
+}
+#endif
diff --git a/core/src/mock/mock_processor.cpp b/core/src/mock/mock_processor.cpp
index 564465f5c4c..570553264d6 100644
--- a/core/src/mock/mock_processor.cpp
+++ b/core/src/mock/mock_processor.cpp
@@ -3,7 +3,7 @@
Description: This is a test implementation of the keyboard processor API to
enable testing API clients against a basic keyboard and give
them something to link against and load.
- TODO: Add a mecahnism to trigger output of PERSIST_OPT &
+ TODO: Add a mechanism to trigger output of PERSIST_OPT &
RESET_OPT actions items, options support and context matching.
Create Date: 17 Oct 2018
Authors: Tim Eves (TSE)
diff --git a/core/tests/unit/ldml/test_unicode.cpp b/core/tests/unit/ldml/test_unicode.cpp
index b36f1a3e385..937125d1727 100644
--- a/core/tests/unit/ldml/test_unicode.cpp
+++ b/core/tests/unit/ldml/test_unicode.cpp
@@ -171,8 +171,9 @@ const std::string &block_unicode_ver) {
// the cxx_icu can come from the Ubuntu environment, so do not depend on it
// for now.
+ // TODO: Resolve with ICU4C 76 in #12398
//assert_basic_equal(node_icu_unicode_major, cxx_icu_unicode_major);
- assert_basic_equal(node_icu_unicode_major, block_ver_major);
+ //assert_basic_equal(node_icu_unicode_major, block_ver_major);
// seems less important if the C++ ICU verison matches the Node.js ICU version.
//assert_basic_equal(cxx_icu_major, node_icu_major);
diff --git a/developer/docs/api/etc/kmc-model.api.md b/developer/docs/api/etc/kmc-model.api.md
index df76354c238..51ea5dffb8d 100644
--- a/developer/docs/api/etc/kmc-model.api.md
+++ b/developer/docs/api/etc/kmc-model.api.md
@@ -4,7 +4,7 @@
```ts
-///
+///
import { CompilerCallbacks } from '@keymanapp/common-types';
import { CompilerEvent } from '@keymanapp/common-types';
diff --git a/developer/docs/help/context/about-tike.md b/developer/docs/help/context/about-tike.md
new file mode 100644
index 00000000000..550a5d7410f
--- /dev/null
+++ b/developer/docs/help/context/about-tike.md
@@ -0,0 +1,8 @@
+---
+title: About Dialog
+---
+
+![About dialog](../images/ui/frmAboutTike.png)
+
+The About dialog displays copyright and registration information for
+Keyman Developer, and has a link to the Keyman website.
\ No newline at end of file
diff --git a/developer/docs/help/context/character-map.md b/developer/docs/help/context/character-map.md
new file mode 100644
index 00000000000..d300455a2b4
--- /dev/null
+++ b/developer/docs/help/context/character-map.md
@@ -0,0 +1,136 @@
+---
+title: Using the Character Map
+---
+
+![Character Map tool](../images/ui/frmCharacterMapNew.png)
+
+To display the Character Map:
+
+- Press Ctrl + Shift + c
+- Select **View** → **Character Map**
+
+The Character Map will appear by default docked to the right hand side
+of the IDE but can be undocked or re-docked on any side of the window.
+
+### Using the Character Map
+
+To insert a character:
+1. Position the text cursor where you want to insert the character
+2. Locate the desired character in the Character Map and insert it by:
+ - selecting the character and pressing the Enter key, or
+ - double-clicking on the character, or
+ - right-clicking on the character and selecting Insert Character.
+
+ > ### Note
+ If the Insert Mode is set to Code or Name, pressing Enter or
+ double-clicking on the character will insert the Unicode code or
+ name of the character, not the character itself. These options are
+ most useful when writing or editing keyboard source files (.KMN).
+
+### Insert Modes
+
+There are three insert modes: Code, Character and Name:
+
+- Code: inserts the character's Unicode code
+- Character: inserts the character itself
+- Name: inserts the character's Unicode name
+
+To change the insert mode:
+
+1. Right-click on the Character Map.
+2. Highlight the Insert Mode submenu.
+3. Choose the desired mode from the Insert Mode submenu.
+
+### Character Map Options
+
+When you right click on the Character Map, the following menu options
+are displayed:
+
+Insert Character
+: Inserts the selected character at the cursor position in your
+ document.
+
+Filter...
+: Displays the Filter dialog box. See [Filtering](#toc-filtering) below.
+
+Goto...
+: Displays the Goto dialog box. See Using [Goto](#toc-goto) below.
+
+Font...
+: Allows you to choose a different font for viewing the Character Map.
+ Note: This does not affect the font being used in your document!
+
+Insert Mode
+: Allows you to select the character insert mode you want. See [Insert
+ Modes](#toc-insert-modes) above.
+
+Display Quality
+: Allows you to select a different font display quality for the
+ Character Map. The options are:
+ - Plain: no font smoothing
+ - Antialiased: uses the Windows standard font smoothing
+ - ClearType: uses the Windows ClearType font smoothing method
+
+### Characters and Fonts
+
+When a character is not available in the selected font, the Character
+Map will fall back to **Code2000**, **Code2001**, **Code2002**, **Arial Unicode MS** and
+**Lucida Sans Unicode** fonts. If the character is not found in these
+fallback fonts, the Character Map will use font linking to attempt to
+find a character from other fonts installed on the system, and as a last
+resort, will show a square box if no suitable font can be found.
+
+If a character is not displayed from other than the selected font, it
+will be shown in blue instead of black.
+
+### Filtering
+
+![Character Map Filter dialog](../images/ui/frmCharacterMapFilter.png)
+
+The Character Map can be filtered by Unicode character name or character
+range. Filters can be entered in the Filter By field at the bottom of
+the Character Map; a quick reference user interface dialog to help you
+edit the filter is available by clicking the \[...\] button.
+
+When no filter is applied, the Character Map will show all characters
+from the Unicode 8.0 standard.
+
+All filter strings are case insensitive. Character names use the letters
+`A-Z`, numerals `0-9`, and punctuation underscore (`_`) and hyphen
+(`-`).
+
+Spaces between words in the filter are represented either by underscore
+(`_`) or space (` `) and can be used interchangeably.
+
+A filter can be for a range or on the Unicode Character Name.
+
+The filter format for a range is: `[U+]XXXX-[U+]YYYY`, where `U+` is
+optional, `XXXX` is the starting Unicode value and `YYYY` is the
+finishing Unicode value.
+
+The filtering options for Unicode Character Names are:
+
+### Examples
+
+| | |
+|--------------------------|-------------------------|
+| Filter | Result |
+| `LATIN CAPITAL LETTER A` | Finds all Latin capital A variations |
+| `LATIN * LETTER A` | finds all Latin capital and lower case A variations |
+| `LATIN * LETTER [AEIOU]` | finds all Latin A,E,I,O or U vowel combinations |
+| `LAO` | finds all characters with names starting in "LAO" in the current font |
+| `YI_` | finds all characters with names starting in "YI " - you must use `_` (underscore) and not `( )` space at the end of a search string. Note that without the `_`, this search also matches YIN_YANG. |
+| `LATIN * LETTER A$` | finds only "a" and "A" |
+| `1000-119F` | finds all characters between U+1000 and U+119F (inclusive) - the Myanmar alphabet in this case |
+
+### Goto
+
+The Goto tool can be used to move directly to a specific Unicode
+character or value.
+
+1. Right click on a character and select Goto. The **Enter Unicode character value or name** dialog box will be displayed.
+2. Enter the Unicode character value - e.g. `006B` or `U+006B` OR enter the start of the character name, e.g. `LAO_LETTER` will go
+ to **LAO_LETTER_KO**, being the first character that matches. You
+ can substitute spaces for underscores if you prefer.
+3. Click OK. The character map will attempt to move to the character you specified.
diff --git a/developer/docs/help/context/debug.md b/developer/docs/help/context/debug.md
new file mode 100644
index 00000000000..07c3bb9e143
--- /dev/null
+++ b/developer/docs/help/context/debug.md
@@ -0,0 +1,165 @@
+---
+title: Debug Window
+---
+
+![Debug window - Debug State](../images/ui/frmKeymanWizard_Debug_State.png)
+
+The debug window is shown at the bottom of the keyboard editor when
+debugging the keyboard. There are several user interface areas: The
+debugger input window, the debugger status window, a debugger toolbar
+(usually docked under the menu), and the status bar will show the
+current debugger status.
+
+## Debugger input window
+
+![Debug window - Debug State](../images/ui/frmDebug.png)
+
+The debugger input window is used for typing input to test the keyboard.
+In the top half of this window, input you type while testing your
+keyboard will be displayed, exactly the same as in use, with one
+exception: deadkeys will be shown visually with an
+![OBJ](../images//ui/obj.gif) symbol.
+
+The lower half of the window shows a grid of the characters to the
+virtual left of the insertion point, or the selected characters if you
+make a selection. Deadkeys will be identified in the grid. The grid will
+show characters in right-to-left scripts in backing store order, from
+left to right. If there are more characters in your text than can fit on
+the screen, then only those that fit will be shown in the grid.
+
+## Debug controls
+
+The debug menu and debug toolbar control the debugger.
+
+![Debug toolbar](../images/ui/Debug_Toolbar.png)
+
+![Debug menu](../images/ui/Debug_Menu.png)
+
+Set/Clear breakpoint
+: A breakpoint can be put on a line of code to ask Keyman Developer to
+ stop and show what is happening in the keyboard layout when that
+ line is reached. Typically, you would put a breakpoint on a
+ particular rule, so you could debug an unexpected behaviour on that
+ rule.
+
+Start Debugging
+: Starts the debugger
+
+Stop Debugger
+: Stops the current debug session
+
+Single Step Mode
+: When this option is active, you will step through every group and
+ rule that your keyboard applies when a keystroke is pressed. The
+ Step Forward and Run controls are only relevant in Single Step Mode.
+
+Step Forward
+: Move to the next step in your keyboard code.
+
+Run
+: Stop processing the current keystroke event in single step mode
+ (unless a breakpoint is hit). The next keystroke event will drop
+ into single step mode again.
+
+Pause
+: Use the Pause button or press Shift + Esc to pause the debugger. When
+ the debugger is paused, it will not accept any input, and ordinary shortcut keys (Shift + F5 , Alt + Tab , etc.) will function as usual. Press Pause again to resume debugging.
+
+
+
+## Debug State box
+
+The debug state box shows the internal state of the keyboard
+interpreter.
+
+### State
+
+![Debug window - Debug State](../images/ui/frmKeymanWizard_Debug_State.png)
+
+This window shows the current keystroke state, and the sequence of
+keystrokes that were typed to arrive at this state. Clearing the text in
+the debug window will also clear the keystroke log; as will pressing the
+Restart button.
+
+The Restart button is disabled while stepping through an event.
+
+### Elements
+
+![Debug window - Debug Elements](../images/ui/frmKeymanWizard_Debug_Elements.png)
+
+This shows the elements that make up rule currently being processed: the
+context, the key, and also what the output will be. If the rule uses
+stores, the contents of the store will be shown in the right-hand
+column, with the matched letter in red.
+
+### Call Stack
+
+![Debug window - Debug Call stack](../images/ui/frmKeymanWizard_Debug_CallStack.png)
+
+Here all the lines that have been processed to this point are shown in a
+list. You can double-click on any entry in the list to display the line
+in the keyboard source.
+
+### Deadkeys
+
+![Debug window - Debug Deadkeys](../images/ui/frmKeymanWizard_Debug_Deadkeys.png)
+
+This lists all the deadkeys that are currently in the context. You can
+select one from the list to see it highlighted in the debug input box.
+This information can also be seen in the character grid in the lower
+half of the debugger input window.
+
+### Regression Testing
+
+![Debug window - Debug Regression Test](../images/ui/frmKeymanWizard_Debug_RegressionTest.png)
+
+The idea in regression testing is to record a sequence of keystrokes and
+the output the keyboard produced, in order to test for the same
+behaviour when you make changes to the keyboard.
+
+Use Start Log/Stop Log to record the input and output. You can then use
+Start Test to run the test again, or go the Options menu to clear the log,
+or save or load a test, or use the batch mode to run several tests in a
+row.
+
+If the output produced while running a test is different to that stored
+when recording it, Keyman will halt the test on the line where the
+failure occurred, and activate Single Step mode.
+
+### Status bar
+
+The input status is shown in the second pane of the status bar. It can
+show one of the following messages:
+
+Ready for input
+: The debugger is waiting for more input
+
+Focused for input
+: The debugger is waiting for more input, and the Debug Input window
+ is active
+
+Paused
+: The debugger is paused
+
+Receiving events
+: The debugger is processing input
+
+Debugging
+: The debugger is active during Single Step mode or after a breakpoint
+
+## Test mode
+
+Enter test mode by clicking the **Test Mode**
+button on the debugger toolbar.
+
+The test mode lets you test your keyboard without the debugger being
+active. This lets you test functionality that is not available within
+the debugger - primarily [IMX code](../guides/develop/imx/).
+
+Once in test mode, no debugger controls are available.
+
+You can see the character codes for the current or selected characters
+in the status bar, which can be useful when debugging your keyboard.
diff --git a/developer/docs/help/context/edit-language-example.md b/developer/docs/help/context/edit-language-example.md
new file mode 100644
index 00000000000..0a2307bf027
--- /dev/null
+++ b/developer/docs/help/context/edit-language-example.md
@@ -0,0 +1,44 @@
+---
+title: Add/Edit Language Example Dialog
+---
+
+![Add/Edit Language Example dialog](../images/ui/frmEditLanguageExample.png)
+
+This dialog allows you to create or edit example key sequences to demonstrate
+how to use your keyboard. Examples should be relatively short -- one to five
+words is ideal, but if possible, should demonstrate sequences that may be hard
+for users to discover on their own.
+
+These language examples can be seen live, for example, on
+[keymanweb.com](https://keymanweb.com).
+
+## Language tag
+
+Enter the BCP 47 language tag that this example corresponds to. It should be a
+language tag that is listed in the package file.
+
+## Key sequence
+
+This is a space separated list of keys. For spacebar, use `space`. For modifier
+key combinations, use the modifier key(s) followed by `+` and the key that they
+are pressed together with, e.g. `right-alt+shift+A`.
+
+* modifiers indicated with "+"
+* spacebar is "space"
+* plus key is "shift+=" or "plus" on US English (all other punctuation as per
+ key cap).
+* Hardware modifiers are: "shift", "ctrl", "alt", "left-ctrl", "right-ctrl",
+ "left-alt", "right-alt"
+* Key caps should generally be their character for desktop (Latin script case
+ insensitive), or the actual key cap for touch
+* Caps Lock should be indicated with "caps-on", "caps-off"
+
+## Expected text
+
+Here, put the actual output generated by the key sequence.
+
+## Note
+
+You may include a translation of the text or another note that highlights an
+aspect of how the text is typed.
+
diff --git a/developer/docs/help/context/editor.md b/developer/docs/help/context/editor.md
new file mode 100644
index 00000000000..79e98806c5c
--- /dev/null
+++ b/developer/docs/help/context/editor.md
@@ -0,0 +1,23 @@
+---
+title: Editor Window
+---
+
+Editor windows in Keyman Developer supports standard Windows editing
+keystrokes. Many file formats, including .kmn, .kps, .xml, .html, .js
+and .json, support syntax highlighting. The text editor in Keyman uses
+the Monaco component from Visual Studio Code, so all the functionality
+available in that editor is also available here.
+
+## Special Functionality
+
+Aside from standard Windows keys, the following keystrokes are defined
+for the editor:
+
+| Keystroke | Action |
+|----------------|---------------|
+| Ctrl + Shift + M | Toggle Message Window visibility |
+| Ctrl + Shift + C | Open Character Map |
+| Ctrl + Shift + I | Toggle Character Identifier visibility |
+| Ctrl + F | Find text |
+| Ctrl + H | Replace text |
+| Ctrl + Shift + U | Convert the selection to/from characters to code values|
\ No newline at end of file
diff --git a/developer/docs/help/context/images/banner.png b/developer/docs/help/context/images/banner.png
new file mode 100644
index 00000000000..b141413e345
Binary files /dev/null and b/developer/docs/help/context/images/banner.png differ
diff --git a/developer/docs/help/context/images/character-grid.png b/developer/docs/help/context/images/character-grid.png
new file mode 100644
index 00000000000..dbe78ceca33
Binary files /dev/null and b/developer/docs/help/context/images/character-grid.png differ
diff --git a/developer/docs/help/context/images/config.png b/developer/docs/help/context/images/config.png
new file mode 100644
index 00000000000..ad8a3710315
Binary files /dev/null and b/developer/docs/help/context/images/config.png differ
diff --git a/developer/docs/help/context/images/device-menu.png b/developer/docs/help/context/images/device-menu.png
new file mode 100644
index 00000000000..0c50b2e59af
Binary files /dev/null and b/developer/docs/help/context/images/device-menu.png differ
diff --git a/developer/docs/help/context/images/hamburger-menu.png b/developer/docs/help/context/images/hamburger-menu.png
new file mode 100644
index 00000000000..d014314192a
Binary files /dev/null and b/developer/docs/help/context/images/hamburger-menu.png differ
diff --git a/developer/docs/help/context/images/keyboard-menu.png b/developer/docs/help/context/images/keyboard-menu.png
new file mode 100644
index 00000000000..cb15d6d3469
Binary files /dev/null and b/developer/docs/help/context/images/keyboard-menu.png differ
diff --git a/developer/docs/help/context/images/lexical-model-test.png b/developer/docs/help/context/images/lexical-model-test.png
new file mode 100644
index 00000000000..4b434fc568f
Binary files /dev/null and b/developer/docs/help/context/images/lexical-model-test.png differ
diff --git a/developer/docs/help/context/images/model-menu.png b/developer/docs/help/context/images/model-menu.png
new file mode 100644
index 00000000000..4572b234f6a
Binary files /dev/null and b/developer/docs/help/context/images/model-menu.png differ
diff --git a/developer/docs/help/context/images/on-screen-keyboard.png b/developer/docs/help/context/images/on-screen-keyboard.png
new file mode 100644
index 00000000000..4aaf2aef520
Binary files /dev/null and b/developer/docs/help/context/images/on-screen-keyboard.png differ
diff --git a/developer/docs/help/context/images/server-iphone.png b/developer/docs/help/context/images/server-iphone.png
new file mode 100644
index 00000000000..d6ce6589034
Binary files /dev/null and b/developer/docs/help/context/images/server-iphone.png differ
diff --git a/developer/docs/help/context/images/status-bar.png b/developer/docs/help/context/images/status-bar.png
new file mode 100644
index 00000000000..7439322aab5
Binary files /dev/null and b/developer/docs/help/context/images/status-bar.png differ
diff --git a/developer/docs/help/context/images/taskbar-menu.png b/developer/docs/help/context/images/taskbar-menu.png
new file mode 100644
index 00000000000..3c352a24a7d
Binary files /dev/null and b/developer/docs/help/context/images/taskbar-menu.png differ
diff --git a/developer/docs/help/context/images/taskbar.png b/developer/docs/help/context/images/taskbar.png
new file mode 100644
index 00000000000..8f4611fe2e3
Binary files /dev/null and b/developer/docs/help/context/images/taskbar.png differ
diff --git a/developer/docs/help/context/images/text-area.png b/developer/docs/help/context/images/text-area.png
new file mode 100644
index 00000000000..4255e550196
Binary files /dev/null and b/developer/docs/help/context/images/text-area.png differ
diff --git a/developer/docs/help/context/index.md b/developer/docs/help/context/index.md
new file mode 100644
index 00000000000..04028b35c1e
--- /dev/null
+++ b/developer/docs/help/context/index.md
@@ -0,0 +1,59 @@
+---
+title: Context Help
+---
+
+[Project Window](project)
+
+[Keyboard Editor](keyboard-editor)
+
+[Model Editor](model-editor)
+
+[Debug Window](debug)
+
+[LDML Keyboard Debug Window](ldml-debug)
+
+[LDML Keyboard Editor](ldml-editor)
+
+[Package Editor](package-editor)
+
+[Add/Edit Language Example dialog](edit-language-example)
+
+[Text Editor](editor)
+
+[New File Dialog](new)
+
+[New Project Dialog](new-project)
+
+[New Project Parameters Dialog](new-project-parameters)
+
+[New Lexical Model Project Parameters Dialog](new-lm-project-parameters)
+
+[New LDML Project Parmaeters dialog](new-ldml-project-parameters)
+
+[About Dialog](about-tike)
+
+[Virtual Key Identifier](key-test)
+
+[Options Dialog](options)
+
+[KMComp Command-line Options](kmcomp)
+
+[KMConvert Command-line Options](kmconvert)
+
+[kmc Command-line Options](kmc)
+
+[kmlmc Command-line Options](kmlmc)
+
+[kmlmi Command-line Options](kmlmi)
+
+[kmlmp Command-line Options](kmlmp)
+
+[Character Map](character-map)
+
+[Message Window](messages)
+
+[Keyman Developer Server](server) (web test host)
+
+[Keyman Developer](keyman-developer)
+
+[Select Web fonts](select-web-fonts)
diff --git a/developer/docs/help/context/key-test.md b/developer/docs/help/context/key-test.md
new file mode 100644
index 00000000000..d51ed334372
--- /dev/null
+++ b/developer/docs/help/context/key-test.md
@@ -0,0 +1,21 @@
+---
+title: Virtual Key Identifier
+---
+
+![Virtual Key Identifier dialog](../images/ui/frmKeyTest.png)
+
+This dialog lets you check the virtual key code for any key combination
+(except Window reserved key combinations such as
+Alt + Tab ). You can then
+insert the virtual key code into the last active edit window at the
+current cursor position.
+
+Press
+Ctrl / Shift / Alt
+and the key you wish to discover. You can see the virtual key codes for
+left and right Ctrl / Alt combinations by checking the "Distinguish between left and right ctrl/alt" checkbox.
+
+To close the dialog, click the Close button or press
+Shift + Esc .
+
+Press Shift + Enter to insert the current virtual key code into your source at the insertion point.
\ No newline at end of file
diff --git a/developer/docs/help/context/keyboard-editor.md b/developer/docs/help/context/keyboard-editor.md
new file mode 100644
index 00000000000..ef43a9b4e87
--- /dev/null
+++ b/developer/docs/help/context/keyboard-editor.md
@@ -0,0 +1,632 @@
+---
+title: Keyboard Editor
+---
+
+The keyboard editor is the heart of Keyman Developer. This window allows
+you to edit all aspects of a keyboard layout, from the visual layout of
+the keyboard, to logic rules, to touch interactions, and more.
+
+The editor is divided into multiple tabs. Initially, only three tabs
+will be visible: Details, Layout, and Build. A keyboard can have
+multiple source files, which are all managed within this one editor.
+
+The following image shows the first tab of the editor, the Details tab,
+for a brand new keyboard, with those three initial tabs.
+
+![Keyboard Editor - New file, Details tab](../images/ui/frmKeymanWizard_New.png)
+
+> ### Note
+Users of earlier versions of Keyman Developer may initially have trouble
+finding tabs such as the Icon tab or the On-Screen tab, as they are not
+initially visible. The Features grid on the Details tab allows you to
+add these extra features into the keyboard.
+
+## Keyboard component files
+
+A keyboard contains multiple source files. The following table lists the
+file types and which tabs are used to edit them. The filenames shown are
+defaults and can be modified, by editing the corresponding line in the
+source file (and renaming the file). However, where possible the default
+names should be used.
+
+| File type | Tabs | Feature | Store name | Description |
+|-------------------|-----------------|--------------------|----------------------|------------------------|
+| `file.kmn` | Details, Layout | | The primary keyboard source file. Required. | |
+| `file.kvks` | On-Screen | Desktop On-Screen Keyboard | [`&visualkeyboard`](/developer/language/reference/visualkeyboard) | The visual presentation of the keyboard for desktop computers. |
+| `file.ico`, `file.bmp` | Icon | Icon | [`&bitmap`](/developer/language/reference/bitmap) | An icon that represents the keyboard in the user interface on desktop computers. |
+| `file.keyman-touch-layout` | Touch Layout | Touch-Optimised Keyboard | [`&layoutfile`](/developer/language/reference/layoutfile) | The touch layout visual description file |
+| `file-code.js` | Embedded JS | Embedded Javascript | [`&kmw_embedjs`](/developer/language/reference/kmw_embedjs) | Additional Javascript code for [IMX](../guides/develop/imx) functionality |
+| `file.css` | Embedded CSS | Embedded CSS | [`&kmw_embedcss`](/developer/language/reference/kmw_embedcss) | Additional CSS stylesheet for custom touch and web layout styling |
+| `file-help.htm` | Web Help | Web Help | [`&kmw_helpfile`](/developer/language/reference/kmw_helpfile) | HTML file that replaces the On Screen Keyboard on desktop web layouts |
+| `file-codes.txt` | Include Codes | Include Codes | [`&includecodes`](/developer/language/reference/includecodes) | Code dictionary for use with [named constants](/developer/language/guide/constants) |
+
+Three tabs include both visual and code editors: the Layout, Touch
+Layout and JSON Metadata tabs. Changes to one view will be immediately
+reflected in the other view. The code in the Layout tab is important
+because that reflects both the Details and the Layout tab.
+
+## Details tab
+
+![Keyboard Editor - Details tab](../images/ui/frmKeymanWizard_Details.png)
+
+The Details tab grows as you add more options to a keyboard. The fields
+here are:
+
+Name
+: The name of the keyboard, corresponding to the [`&name` store](/developer/language/reference/name).
+
+Targets
+: The intended target devices and operating systems, corresponding to
+ the [`&targets` store](/developer/language/reference/targets).
+ Changing the targets will hide or show parts of the Details and
+ Compile tabs. If no targets are selected, Keyman Developer will
+ treat the keyboard as a Windows-only keyboard, for backward
+ compatibility reasons.
+
+Copyright
+: Enter the details of the copyright owner for the keyboard.
+ [`©right` store](/developer/language/reference/copyright).
+
+Message
+: Enter a message that will be shown at install time for the keyboard
+ (on Windows, macOS). [`&message` store](/developer/language/reference/message).
+
+Keyboard is right-to-left
+: Check this box to indicate that the keyboard is a right-to-left
+ keyboard, for web and touch layouts. [`&kmw_rtl` store](/developer/language/reference/kmw_rtl).
+
+Web Help Text
+: A single line of text, with basic HTML allowed, shown at the bottom
+ of the desktop web On Screen Keyboard. [`&kmw_helptext` store](/developer/language/reference/kmw_helptext).
+
+Keyboard version
+: The version of the keyboard. Read the reference documentation for
+ [`&keyboardversion` store](/developer/language/reference/keyboardversion) to understand
+ the legal values for this field. This is not the same as the
+ [`&version` store](/developer/language/reference/version), which
+ controls the Keyman (and Keyman Developer) version for which a
+ keyboard is designed.
+
+Comments
+: This field corresponds to the first lines of comments in the
+ keyboard source, and is visible only to the keyboard designer.
+
+Features
+: The Features grid controls which additional file components are
+ included in the keyboard, as listed in the Component Files section
+ above. Each of the features relates to a system store. Adding a
+ feature will add an extra tab to the editor, and add the
+ corresponding store to the keyboard source. Removing a feature will
+ not delete the component file, but will just remove the store from
+ the keyboard source.
+
+## Layout tab
+
+![Keyboard Editor - Layout tab, Design view](../images/ui/frmKeymanWizard_Layout_Design.png)
+
+The Layout tab gives you a simple interface to quickly create a keyboard
+using a visual representation of a desktop/laptop computer keyboard. You
+can drag and drop characters from the character map to create keyboard
+layouts. You cannot access most of Keyman's more powerful features from
+the Layout Design view, but it will be useful to get you started on your
+design.
+
+Each key can have zero or more characters assigned to it. Each key can
+be assigned a different set of characters for each shift state. You can
+change the shift state by clicking on the Shift, Ctrl, and Alt keys.
+
+There are two ways to assign characters to the keys:
+
+- Click on a key, and then type the character's code into the Unicode
+ Character Value(s) field, or the type/paste the character itself
+ into the Output character(s) field.
+- Select the character from the Character Map and drag and drop it
+ onto the appropriate key. This will set the key to output that
+ character.
+
+> ### Hint
+ To add the character to a key with existing characters, hold
+Ctrl while dropping it onto the key.
+
+
+> ### Note
+ Any key that does not have a character assigned to it will output what the selected Windows layout specifies.
+
+Distinguish between left and right Ctrl/Alt
+: A Keyman keyboard can treat left and right Ctrl and Alt identically,
+ or it can distinguish between them. The Design view allows one or
+ the other mode (in Code view, you can use both interchangeably).
+
+Display 102nd Key (as on European keyboards)
+: European keyboards have one extra key that is not on US keyboards.
+ This key is positioned to the right of the left shift key. Some
+ other keyboards have additional keys; these are not shown on the
+ layout designer. If you have a European layout selected as your
+ Windows layout, the 102nd key will always be visible in the
+ designer. When a European layout is selected as your Windows layout,
+ the shape of the Enter key will also change to take two rows, and
+ the backslash key will move down one row, but not otherwise change
+ in behaviour.
+
+You can press and release Ctrl to select
+another key on the keyboard using your keyboard.
+
+
+
+![Keyboard Editor - Layout tab, Code view](../images/ui/frmKeymanWizard_Layout_Code.png)
+
+The Code view shows the source code of the keyboard file. This is where
+all the information in the Details and Layout tab is stored, and
+additional logic and complex keyboard layouts will be edited entirely in
+the Code view.
+
+See the [Editor topic](editor) for more information on how to use the
+editor shown within this view.
+
+## On-Screen tab
+
+![Keyboard Editor - On-Screen tab](../images/ui/OnScreenKeyboard.png)
+
+This tab allows you to edit the visual representation of your keyboard
+layout. The content on this tab is stored in the .kvks file associated
+with your keyboard. The visual representation is used only in desktop
+and desktop web; however if no touch layout is defined, this layout will
+be synthesized into a touch layout automatically.
+
+An On-Screen keyboard is optional but in most keyboards is recommended.
+The On-Screen keyboard may not always match the actual layout
+identically, because you may choose to hide some of the details of
+encoding from the interface presented to the user. The user can also
+choose to print the layout.
+
+This keyboard layout can also be printed or included in HTML or other
+documentation. The editor allows you to export the file to HTML, PNG or
+BMP formats.
+
+Fill from layout
+: Compiles the keyboard file, and processes each possible keystroke in
+ the keyboard file to automatically generate an On-Screen Keyboard
+ that matches the layout. This can be used to effectively
+ pre-populate the On-Screen Keyboard and reduces the complexity of
+ designing it from scratch.
+
+Import
+: Imports an On-Screen Keyboard from an XML file.
+
+Export
+: Exports the On-Screen Keyboard to an XML file.
+
+Text
+: The key cap for the selected key
+
+Bitmap
+: A bitmap that is displayed on the selected key (not recommended, as
+ it will not scale cleanly)
+
+Display underlying layout characters
+: If checked, then the base or underlying layout will show in small
+ letters at the top left of each key cap. This can be helpful to
+ provide users with a guide as to the position of each key on the
+ keyboard, especially for non-Latin scripts.
+
+Distinguish between left and right Ctrl/Alt
+: If checked, treats the left and right Ctrl/Alt as separate layers
+
+Display 102nd Key (as on European keyboards)
+: European keyboards have one extra key that is not on US keyboards.
+ This key is positioned to the right of the left shift key. Some
+ other keyboards have additional keys; these are not shown on the
+ layout designer. If you have a European layout selected as your
+ Windows layout, the 102nd key will always be visible in the
+ designer; ensure you select this checkbox if you want the 102nd key
+ to always be visible to end users of the layout, irrespective of
+ their selected base layout. When a European layout is selected as
+ your Windows layout, the shape of the Enter key will also change to
+ take two rows, and the backslash key will move down one row, but not
+ otherwise change in behaviour.
+
+Auto-fill underlying layout
+: When the Fill from layout button is clicked, if this option is
+ checked, then keys without corresponding rules in the Layout will be
+ filled with the base layout character.
+
+## Touch Layout tab
+
+![Keyboard Editor - Touch Layout tab, Design view](../images/ui/TouchLayout_Design.png)
+
+The Touch Layout tab is used to create the visible representation of the
+keyboard layout for touch devices. It works similarly to the On Screen
+Keyboard Editor conceptually, but has a number of additional features
+specific to touch. Keys on the touch layout trigger rules within the
+normal Keyman keyboard; if no rule is defined for a given key, it will
+be given output if it has a standard code beginning with `K_`, or if it
+is a Unicode value code, starting with `U_`.
+
+Follow this
+[guide](../guides/develop/touch-keyboard-tutorial/making-touch-keyboard)
+for learning how to create a Touch Layout keyboard.
+
+In Design view, the editor can show a number of different device types,
+including iPhone and iPad, in different orientations, to allow you to
+visualize the keyboard layout before you load it onto a device. The
+following image shows all aspects of the touch design view.
+
+![Keyboard Editor - Touch Layout tab, Design view closeup](../images/ui/TouchLayout_Design_2.png)
+
+### Left sidebar controls
+
+Template...
+: The Template button allows you to choose a standard layout of keys
+ from a predefined set. These layouts move the standard set of keys
+ around and between layers to reduce the number of keys on each
+ layer, optimising for mobile phone or other sizes. When Keyman
+ Developer switches to a new template, it will transfer key
+ definitions to the new layout as far as possible, but if keys on the
+ current layout are not in the new template, their definitions will
+ be lost. Therefore, selecting an appropriate template early in the
+ development process is suggested.
+
+Import from On Screen
+: If you have an existing On-Screen Keyboard for your keyboard layout,
+ importing the design from the On-Screen Keyboard can reduce the
+ development time considerably. Once you have imported, you will
+ probably want to select a new template to transform the
+ desktop-oriented design to a tablet or phone design automatically.
+
+View Controls
+: This selector allows you to choose the simulated view of the
+ keyboard, either landscape or portrait, for a number of devices. The
+ presented keyboard may not be 100% identical to the final layout as
+ seen on the device, but this allows you to get a feel for the design
+ before testing.
+
+Platform
+: The platform controls allow you to select and add or remove platform
+ support for a given layout. If a platform is not defined, then
+ Keyman Engine will transform the layout from another platform
+ automatically, so you don't necessarily need to define layouts for
+ each platform. If the 'Display underlying' checkbox is checked, then
+ the base or underlying layout will show in small letters at the top
+ left of each key cap. This can be helpful to provide users with a
+ guide as to the position of each key on the keyboard, especially for
+ non-Latin scripts.
+
+Layer
+: Each layout for each platform is made up of one or more layers of
+ keys. Each layer can have a default shift state associated with it,
+ which allows keys to trigger specific rules in the Layout code.
+
+### Keyboard area
+
+On the right of the sidebar is the keyboard design area. This shows a device with
+a presentation of the key layout. Within the key layout, you can click
+on any key to edit it. There are a number of controls:
+
+Red circle with an X
+: This button to the top right of the key will delete the key from the
+ row; if it is the last key on the row, the entire row will be
+ deleted.
+
+Green arrow with a +
+: These buttons will insert a key to the left or right, or a single
+ key on a new row above or below the selected key. Adding extra keys
+ scales the entire keyboard.
+
+Dragging the right hand side of the key
+: Dragging the right hand side of the key will resize the key;
+ resizing the key will rescale the entire keyboard so it still fits
+ within the device screen.
+
+Metrics
+: The metrics displayed on the right hand side of the keyboard show
+ the virtual width of each row of the keyboard, and the number of
+ keys. For small devices, 10 keys is the maximum recommended number
+ in a row; each key has a standard width of 100. While each row can
+ be a different length, the last key in the row will be stretched to
+ balance the final design; 'spacer' keys can be used to leave a
+ visual gap on the right hand side of the keyboard. You should aim to
+ make each row the same total width for consistent results.
+
+### Right sidebar controls
+
+Next to the keyboard area is another control bar for editing details of
+the selected key.
+
+Keycap Value
+: Assign a type to a key. This is to specifies which keys are Text, AltGr, Shift, Menu, TabLeft, ZWSp and more.
+
+Text
+: You can drop characters from Character Map directly into the key cap
+ edit box, or type directly.
+
+Text Unicode
+: Clicking on a text unicode will focus the unicode edit box
+ instead of the text edit box.
+
+Hint and Hint Unicode
+: Add any Unicode to indicate the presence of long-press keys for that key, and the hint will be visible in the top right corner of the key.
+
+Key Type
+: The general appearance of each key is determined by the key type,
+ which is selected (in Keyman Developer 10) from a drop-down list:
+
+ | Key Type | Description |
+ |----------------------|----------------------------|
+ | Default (normal) key | A standard letter or character key on the keyboard |
+ | Special (shift) key | The grey control type keys, layer shifts, Enter, Backspace, etc. |
+ | Special (Active) | A variation on the Special keys, which shows a highlight, typically used to indicate that a shift key for example is down. |
+ | Dead-key | While this key type has no difference in function to default keys, it will have a different style to indicate that it is different; the logic in the keyboard source may well be a deadkey, for example. |
+ | Blank | This type of key cannot be selected in use and shows as a blank key. |
+ | Spacer | Leaves a space in the keyboard at the point it is inserted, in the background colour of the keyboard area. |
+
+Modifier
+: Determines the state that Keyman Engine will receive from the
+ key stroke, such as Default, Right-Alt, Shift, Right-Alt Shift, or Caps. This makes it simpler to design a single keyboard that works across both desktop and mobile interfaces.
+
+ID
+: Each key must be given an identifying ID which is unique to
+ the key layer. ID by and large correspond to the virtual key
+ codes used when creating a keyboard program for a desktop keyboard,
+ and should start with `K_`, for keys mapped to standard Keyman
+ virtual key names, e.g. `K_HYPHEN`, and `T_` or `U_` for
+ user-defined names, e.g. `T_ZZZ`.
+ If keyboard rules exist matching the key code in context, then the output from the key will be
+ determined by the processing of those rules. It is usually best to
+ include explicit rules to manage the output from each key, but if no
+ rules matching the key code are included in the keyboard program,
+ and the key code matches the pattern `U_xxxx[_yyyy...]`
+ (where `xxxx` and `yyyy` are 4 to 6-digit hex strings), then the
+ Unicode characters `U+xxxx` and `U+yyyy` will be output. As of
+ Keyman 15, you can use more than one Unicode character value in the
+ id (earlier versions permitted only one). The ID is always
+ required, and a default code will usually be generated automatically
+ by Keyman Developer.
+
+ - `K_xxxx` is used for a standard Keyman for Windows's key name, e.g.
+ `K_W`, `K_ENTER`. You cannot make up your own `K_xxxx` names.
+ Many of the `K_` ids have overloaded output behaviour, for
+ instance, if no rule is matched for `K_W`, Keyman will output
+ 'w' when it is touched. The standard key names are listed in
+ [Virtual Keys and Virtual Character Keys](/developer/language/guide/virtual-keys). Typically,
+ you would use only the "common" virtual ID.
+ - `T_xxxx` is used for any user defined names, e.g. `T_SCHWA`.
+ If you wanted to use it, `T_ENTER` would also be valid. If no
+ rule matches it, the key will have no output behaviour.
+ - `U_####[_####]` is used as a shortcut for a key that
+ will output those Unicode values, if no rule matches it. This is
+ similar to the overloaded behaviour for `K_` ids. Thus `####`
+ must be valid Unicode characters. E.g. `U_0259` would generate a
+ schwa if no rule matches. It is still valid to have a rule such
+ as `+ [U_0259] > ...`
+
+Padding Left
+: Padding to the left of each key can be adjusted, and specified as a
+ percentage of the default key width (100). If not specified, a
+ standard padding of 15% of the key width is used between adjacent
+ keys.
+
+Width
+: The layout is scaled to fit the widest row of keys in the device
+ width, assuming a default key width of 100 units. Keys that are to
+ be wider or narrower than the default width should have width
+ specified as a percentage of the default width. For any key row that
+ is narrower than the widest row, the width of the last key in the
+ row will be automatically increased to align the right hand side of
+ the key with the key with the right edge of the keyboard. However,
+ where this is not wanted, a "spacer" key can be inserted to leave a
+ visible space instead. As shown in the above layouts, where the
+ spacer key appears on the designer screen as a narrow key, but will
+ not be visible in actual use.
+
+Next layer
+: The virtual keys `K_SHIFT`, `K_CONTROL`, `K_MENU`, etc. are normally
+ used to switch to another key layer, which is implied by the ID. The left and right variants of those ID, and also
+ additional layer-switching keys mentioned above (`K_NUMERALS`,
+ `K_SYMBOLS`, `K_CURRENCIES`, `K_ALTGR`) can also be used to
+ automatically switch to the appropriate key layer instead of
+ outputting a character. However, it is sometimes useful for a key to
+ output a character first, then switch to a new layer, for example,
+ switching back to the default keyboard layer after a punctuation key
+ on a secondary layer had been used. Specifying the `nextlayer` for a
+ key allows a different key layer to be selected automatically
+ following the output of the key. Of course, that can be manually
+ overridden by switching to a different layer if preferred.
+
+Drag and drop
+: Keys can be moved around the keyboard by dragging them with the
+ mouse; a ghost landing box will appear beneath the key when it can
+ be dropped. If the key is dropped in an invalid area, no action will
+ occur.
+
+Double-click
+: Double clicking on a key with a "Next Layer" defined will switch to
+ that layer.
+
+### Middle bar controls
+
+#### Long-press keys area
+
+The keys in the long press area cannot be resized but the controls
+otherwise work the same as in the main keyboard area. The keys cannot be
+dragged between the main keyboard area and the long press area. The bar
+below the long press area contains the corresponding controls for the
+long press keys.
+
+![Keyboard Editor - Touch Layout tab, Code view](../images/ui/frmKeymanWizard_TouchLayout_Code.png)
+
+The Code view allows you to edit the JSON source of the touch layout
+file. This makes certain operations simpler, such as batched rearranging
+of keys, or using another text editor to make bulk changes. The Code
+view uses the standard [editor](editor).
+
+The format of the layout file is described in [Creating a touch keyboard layout guide](../guides/develop/creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty).
+
+#### Long-press, Flicks, and Multitaps controls
+
+Underneath the keyboard area is the Long-press, Flicks, and Multitaps controls.
+
+![Long-press, Flicks, and Multitaps layout](../images/ui/LongPress_Flicks_Multitaps.png)
+
+Red circle with an X
+: This button to the top right of the key will delete the key from the
+ row; if it is the last key on the row, the entire row will be
+ deleted.
+
+Green arrow with a + *(Applies to Long-press and Multitaps)*
+: These buttons will insert a key to the left or right, or a single
+ key on a new row above or below the selected key. Adding extra keys
+ scales the entire keyboard.
+
+Drag and drop *(Applies to Long-press and Multitaps)*
+: Keys can be moved around the keyboard by dragging them with the
+ mouse; a ghost landing box will appear beneath the key when it can
+ be dropped. If the key is dropped in an invalid area, no action will
+ occur.
+
+#### Right sidebar controls
+
+Next to the three controls is another control bar for editing details of
+the selected key for different Gesture Type. The only difference between this control and the [keyboard right sidebar](#toc-right-sidebar-controls) controls is:
+* Include Gesture Type box and Default selection check box.
+* **No** Hint, Hint Unicode, Padding Left, and Width.
+
+## Icon tab
+
+![Keyboard Editor - Icon tab](../images/ui/frmKeymanWizard_Icon.png)
+
+The icon tab allows you to edit the icon associated with the keyboard. An external icon editor will allow you to create more sophisticated icons with alpha transparency and multiple resolutions, but this will cover the standard 16x16 256 colour icon, which is all that many keyboards require. The controls at the top left are:
+
+Pencil
+: Freehand drawing with the selected colour; use right mouse button
+ for secondary colour.
+
+Line
+: Draw a straight line
+
+Open box
+: Draw an outline box
+
+Filled box
+: Draw a filled box in current colour
+
+Open circle
+: Draw an outline circle or ellipse
+
+Filled circle
+: Draw a filled circle or ellipse in the current colour
+
+Fill
+: Fill from the current point all contiguous pixels of the same colour
+
+Text
+: Use this tool to add a letter or letters to the icon. Click in the
+ lower left of the icon area and a window opens where you can type
+ the text you want. You can also change the quality of the font as
+ well as the font properties by clicking on the Change button. Note
+ that to change the color of the font, you need to change the color
+ setting in the font properties window.
+
+The icon can be exported or imported to/from various other formats. The
+transparent colour is the top left colour in the grid with an X in the
+box. The icon can be rotated in any direction with the arrow controls
+below the toolbox; this will not lose data from the icon but will
+instead rotate the edge moving off the grid to the other side of the
+grid.
+
+## Embedded JS tab
+
+This tab allows you to edit JavaScript code in the file referenced by
+the [`&kmw_embedjs`](/developer/language/reference/kmw_embedjs) store,
+implementing [IMX functionality](../guides/develop/imx) of the keyboard.
+
+## Embedded CSS tab
+
+This tab allows you to edit the Cascading Style Sheet rules in the file
+referenced by the
+[`&kmw_embedcss`](/developer/language/reference/kmw_embedcss) store for
+the keyboard.
+
+## Web Help tab
+
+This tab allows you to add a custom HTML snippet to replace the desktop
+on screen keyboard, for example useful if the keyboard uses sequences of
+keys to produce characters.
+
+## Include Codes tab
+
+This tab allows you to edit the [named constants](/developer/language/guide/constants) for the keyboard.
+
+## Build tab
+
+![Keyboard Editor - Build tab](../images/ui/BuildTab.png)
+
+A set of common controls is across the top of this tab:
+
+Compile Keyboard
+: You can compile the keyboard for testing and installation by
+ clicking the Compile button, or
+ selecting Keyboard, Compile Keyboard. This will compile the keyboard
+ for all the selected Targets from the Details tab.
+
+Start Debugging
+: To test your keyboard within Keyman Developer, press F5 to start the
+ [debugger](debug), or press the Start Debugging button. This is
+ suitable for testing desktop layouts.
+
+Open source, build, and project folder
+: Opens the folder which contains the keyboard source, build, and project files.
+
+
+
+### Windows, macOS, and Linux Targets
+
+If your keyboard is designed to target Windows and macOS, then the
+following buttons will also be visible:
+
+Install
+: Installs the keyboard for use with Keyman Desktop on your computer
+
+Uninstall
+: Uninstalls the keyboard from Keyman Desktop if previously installed
+
+
+
+### Web and Mobile Targets
+
+If your keyboard is designed to target KeymanWeb, mobile or tablet
+devices, the following controls will also be visible:
+
+Test Keyboard on Web
+: Starts the Keyman Developer Web Server for the current keyboard. This will list the various IP addresses and hostnames that Keyman Developer is listening on
+
+Configure...
+: Pressing this allows the [Options](./options) dialog to pop-up.
+
+Open in browser
+: Starts your default browser with the selected address to allow
+ testing of the keyboard directly
+
+Send to email
+: Sends the list of web addresses as an email so that you can more
+ easily open the test site on your mobile device, or share with
+ someone else on your network.
+
+Copy link
+: Once the IP addresses are shown, feel free to copy any one of the links.
+
+To test on other platforms, open one of the debug host addresses listed
+on your device. Your device will need to be on the same network as the
+Keyman Developer computer.
+
+You can test within the web browser on your device, or in the native app
+if you have it installed: just click the Install button on the debugger
+web page.
+
+The Send to email function makes it easier to open the addresses on your
+mobile device without having to enter them manually.
+
+Learn more about [testing on the web and mobile devices](../guides/test/).
\ No newline at end of file
diff --git a/developer/docs/help/context/keyman-developer.md b/developer/docs/help/context/keyman-developer.md
new file mode 100644
index 00000000000..38544801847
--- /dev/null
+++ b/developer/docs/help/context/keyman-developer.md
@@ -0,0 +1,5 @@
+---
+title: Keyman Developer Main Window
+---
+
+- [Project Window](project)
\ No newline at end of file
diff --git a/developer/docs/help/context/kmc.md b/developer/docs/help/context/kmc.md
new file mode 100644
index 00000000000..088b04ca0d3
--- /dev/null
+++ b/developer/docs/help/context/kmc.md
@@ -0,0 +1,5 @@
+---
+title: kmc Command Line Compiler Toolset
+---
+
+* [kmc documentation](../reference/kmc)
\ No newline at end of file
diff --git a/developer/docs/help/context/kmcomp.md b/developer/docs/help/context/kmcomp.md
new file mode 100644
index 00000000000..0a4b382bbce
--- /dev/null
+++ b/developer/docs/help/context/kmcomp.md
@@ -0,0 +1,7 @@
+---
+title: kmcomp - Command Line Compiler (deprecated)
+---
+
+kmcomp has been replaced by [kmc](../reference/kmc).
+
+* [Migrate from kmcomp to kmc](../reference/kmc/cli/kmcomp-migration)
\ No newline at end of file
diff --git a/developer/docs/help/context/kmconvert.md b/developer/docs/help/context/kmconvert.md
new file mode 100644
index 00000000000..d620841473d
--- /dev/null
+++ b/developer/docs/help/context/kmconvert.md
@@ -0,0 +1,125 @@
+---
+title: KMConvert Command Line Keyboard Conversion Utility
+---
+
+KMConvert generates keyboards and models from templates, and converts keyboard
+layouts between different formats.
+
+The [New Project dialog](new-project) in Keyman Developer provides a graphical
+version of the functionality in KMConvert.
+
+In a default installation of Keyman Developer on Windows, it is located in
+`%ProgramFiles(x86)%\Keyman\Keyman Developer`, and should be on your `PATH`.
+KMConvert will generate a full Keyman template project from the imported layout
+or data, in a new folder named with the ID of the keyboard. It will not
+overwrite an existing folder.
+
+The following parameters are available:
+
+```shell
+kmconvert import-windows -klid [additional-options]
+kmconvert template -id [additional-options]
+kmconvert lexical-model -id-author -id-language -id-uniq [additional-options]
+```
+
+### Available conversion modes
+
+The conversion mode must be specified as the first parameter.
+
+`import-windows`
+: Imports a Windows keyboard into a new Keyman keyboard project. The `-klid`
+parameter is required. This mode only functions on Windows platforms.
+
+`template`
+: Creates a blank keyboard project in the repository template format. The `-id`
+parameter is required.
+
+`lexical-model`
+: Creates a wordlist lexical model project in the repository template format.
+The `-id-author`, `-id-language`, and `-id-uniq` parameters are required.
+
+### General parameters
+
+`-author `
+: Name of author of the keyboard or model, no default.
+
+`-copyright `
+: Copyright string for the keyboard or model, defaults to `Copyright (C)`,
+ used everywhere except license file.
+
+`-full-copyright `
+: Longer copyright string for the keyboard or model, defaults to
+ `Copyright (C) yyyy`, used in license file.
+
+`-languages `
+: Space-separated list of BCP 47 tags, e.g. `en-US tpi-PG`
+
+`-name `
+: Name of the keyboard or model, e.g. `My First Keyboard`, `%s Basic`. Format
+strings are only valid in `import-windows` mode; `%s` will be replaced with the
+friendly English name of the imported keyboard.
+
+`-nologo`
+: Don't show the program description and copyright banner
+
+`-o `
+: The target folder to write the keyboard project into, defaults to `.\`
+
+`-version `
+: Version number of the keyboard or model, defaults to `1.0`
+
+### `template` and `import-windows` parameters
+
+`-id `
+: The id of the keyboard to create. Required for `template`, optional for
+`import-windows`, not valid for `-lexical-model`. In `import-windows` mode, you
+can use a format string with the placeholder `%s` which will be replaced with
+the source filename, e.g. `kbdus`. If not specified, then the source filename
+will form the id of the generated keyboard.
+
+`-targets `
+: Space-separated list of [targets](/developer/language/reference/targets), e.g.
+`linux windows phone`
+
+### `import-windows` parameters
+
+`-klid `
+: The KLID of the keyboard to import, per LoadKeyboardLayout. This must be an
+eight digit hex identifier, such as `00000409` for English (US), kbdus.dll. This
+parameter is only valid for `import-windows` conversion mode.
+
+### Lexical model parameters
+
+`-id-author `
+: Identifier for author of model
+
+`-id-language `
+: Single BCP 47 tag identifying primary language of model
+
+`-id-uniq `
+: Unique name for the model
+
+Note: model identifiers are constructed from parameters:
+`..`
+
+The values for `-id-author` and `-id-uniq` must be valid tokens: lower case a-z, 0-9, and underscore. These should also start with a letter or underscore.
+
+### Examples
+
+Create a lexical model for Tok Pisin:
+
+```shell
+kmconvert lexical-model -id-author james -id-language tpi-PG -id-uniq tokpisin -name "Tok Pisin" -languages tpi-PG
+```
+
+Create a keyboard for Javanese from a template:
+
+```shell
+kmconvert template -id sample -author "James Example" -name "My Javanese Keyboard" -copyright "Copyright (C) James Example" -full-copyright "Copyright (C) 2021 James Example" -languages jv-java
+```
+
+Create a new keyboard based on the Windows German keyboard:
+
+```shell
+kmconvert import-windows -klid 00000407 -id german_plus -name "German Plus" -copyright "Copyright (C) James Example" -full-copyright "Copyright (C) 2021 James Example" -author "James Example"
+```
diff --git a/developer/docs/help/context/kmlmc.md b/developer/docs/help/context/kmlmc.md
new file mode 100644
index 00000000000..33edf500f5c
--- /dev/null
+++ b/developer/docs/help/context/kmlmc.md
@@ -0,0 +1,5 @@
+---
+title: kmlmc - Command Line Lexical Model Compiler (deprecated)
+---
+
+kmlmc has been replaced by [kmc](../reference/kmc).
diff --git a/developer/docs/help/context/kmlmi.md b/developer/docs/help/context/kmlmi.md
new file mode 100644
index 00000000000..dc47cbbc39a
--- /dev/null
+++ b/developer/docs/help/context/kmlmi.md
@@ -0,0 +1,5 @@
+---
+title: kmlmi - Command Line Lexical Model model_info Compiler (deprecated)
+---
+
+kmlmi has been replaced by [kmc](../reference/kmc).
diff --git a/developer/docs/help/context/kmlmp.md b/developer/docs/help/context/kmlmp.md
new file mode 100644
index 00000000000..3442d8f1424
--- /dev/null
+++ b/developer/docs/help/context/kmlmp.md
@@ -0,0 +1,5 @@
+---
+title: kmlmp - Command Line Lexical Model Package Compiler (deprecated)
+---
+
+kmlmp has been replaced by [kmc](../reference/kmc).
diff --git a/developer/docs/help/context/ldml-debug.md b/developer/docs/help/context/ldml-debug.md
new file mode 100644
index 00000000000..e3eb2f717be
--- /dev/null
+++ b/developer/docs/help/context/ldml-debug.md
@@ -0,0 +1,25 @@
+---
+title: LDML Keyboard Debug Window
+---
+
+The LDML keyboard debug window is shown at the bottom of an LDML keyboard editor
+when testing the keyboard. In version 17.0, the LDML keyboard debug window is
+a simple test window, without support for interactive debugging, unlike the
+Keyman keyboard debug window.
+
+## Debugger input window
+
+![Debug window - Debug State](../images/ui/frmDebug.png)
+
+The debugger input window is used for typing input to test the keyboard.
+In the top half of this window, input you type while testing your
+keyboard will be displayed, exactly the same as in use, with one
+exception: deadkeys will be shown visually with an
+![OBJ](../images//ui/obj.gif) symbol.
+
+The lower half of the window shows a grid of the characters to the virtual left
+of the insertion point, or the selected characters if you make a selection.
+Markers will be identified in the grid, but only by numeric value in version
+17.0. The grid will show characters in right-to-left scripts in backing store
+order, from left to right. If there are more characters in your text than can
+fit on the screen, then only those that fit will be shown in the grid.
\ No newline at end of file
diff --git a/developer/docs/help/context/ldml-editor.md b/developer/docs/help/context/ldml-editor.md
new file mode 100644
index 00000000000..94eb3ecc45e
--- /dev/null
+++ b/developer/docs/help/context/ldml-editor.md
@@ -0,0 +1,18 @@
+---
+title: LDML Keyboard Editor Window
+---
+
+![Keyboard Editor - New file, Details tab](../images/ui/frmLDMLEditor.png)
+
+The LDML Keyboard file format is new in Keyman Developer 17. It is an
+XML file, and follows the specification at https://www.unicode.org/reports/tr35/tr35-keyboards.html
+
+The LDML editor in this release is very basic, supporting syntax highlighting
+and basic editing, but specific support for the keyboard3 format is scheduled
+for release in version 18.0.
+
+LDML keyboards can be tested by pressing F5 . This opens the [LDML Debug window](ldml-debug).
+
+The text editor supports standard Windows editing keystrokes. It uses the Monaco
+component from Visual Studio Code, so much of the functionality available in
+that editor is also available here.
diff --git a/developer/docs/help/context/messages.md b/developer/docs/help/context/messages.md
new file mode 100644
index 00000000000..9c9ad133359
--- /dev/null
+++ b/developer/docs/help/context/messages.md
@@ -0,0 +1,18 @@
+---
+title: Message Window
+---
+
+![Message window](../images/ui/frmMessages.png)
+
+The message window appears at the bottom of the screen, or floating in a toolbar window. It contains a list of error and warning messages returned from a compilation session.
+
+You can double click or press Enter to select a
+line corresponding to an error in the window. To open and close the
+window, press Ctrl +Shift +M .
+You can save the messages listed in the window, or clear them, through
+the context menu available by clicking the right mouse button in the
+window (or pressing the Context Menu key).
+
+You can undock and dock the window by dragging its title bar.
+
+The hotkeys for moving to next and previous messages in the list are F4 and Shift +F4 , respectively.
diff --git a/developer/docs/help/context/model-editor.md b/developer/docs/help/context/model-editor.md
new file mode 100644
index 00000000000..2366eaeba9a
--- /dev/null
+++ b/developer/docs/help/context/model-editor.md
@@ -0,0 +1,185 @@
+---
+title: Lexical Model Editor
+---
+
+The lexical model editor allows you to create and maintain lexical
+models for predictive text.
+
+## Lexical model component files
+
+A lexical model can have multiple source files, all of which can be
+managed in this one editor. The source files currently supported by the
+editor are:
+
+| File type | Tabs | Description |
+|-------------------|-----------------|-------------|
+| `file.model.ts` | Details, Source | The primary lexical model source file, a TypeScript module. |
+| `wordlist.tsv` | wordlist.tsv | One or more wordlists, UTF-8 format tab-separated value files. These files are referenced in the .model.ts source file. |
+
+## Details tab
+
+The following image shows the first tab of the editor, the Details tab.
+Changes made to this tab are reflected immediately in the Source tab.
+
+![Model Editor - Details tab](../images/ui/frmModelEditor_Details.png)
+
+The fields here have corresponding definitions in the [.model.ts source file](../reference/file-types/model-ts), which can be seen in the
+**Source** tab:
+
+Format
+: The implementation of the lexical model. This can be either
+ **Wordlist** (`trie-1.0`), or **Custom** (`custom-1.0`). Corresponds
+ to the `format` property in the [.model.ts source file](../reference/file-types/model-ts).
+
+Word breaker
+: What method is used to separate words in text. This can be one of
+ `default`, `ascii`, or `custom`. Corresponds to the `wordBreaker`
+ property in the [.model.ts source file](../reference/file-types/model-ts).
+
+Insert after word
+: Many languages insert a space after a word. Some languages, like Thai or Khmer, do not use spaces. To suppress the space, you may set insertAfterWord to the empty string:
+
+ ```keyman
+ punctuation: {
+ insertAfterWord: "",
+ },
+ ```
+
+[Read more](../guides/lexical-models/advanced/punctuation#toc-customizing-insertafterword)
+
+Quotation marks
+: These are the quotation marks that surround the "keep" suggestion when it's displayed in the suggestion banner. [Read about Quotation marks here](../guides/lexical-models/advanced/punctuation#toc-customizing-quotesforkeepsuggestion).
+
+Right-to-left script
+: Check this box to indicate that the lexical-model is for a right-to-left script.
+
+Language uses casing
+: Tick this box for uppercase, capitalize, or any casing in the predicted word.
+
+Comments
+: This field corresponds to the first lines of comments in the model
+ source, and is visible only to the model designer.
+
+Wordlists
+: The Wordlists grid controls which additional wordlist files are
+ referenced by the model. Adding a wordlist file will add an extra
+ tab to the editor, and add the corresponding reference to the
+ lexical model source. Removing a wordlist will not delete the
+ component file, but will just remove the reference from the lexical
+ model source. Corresponds to the `sources` property in the
+ [.model.ts source file](../reference/file-types/model-ts).
+
+> ### Note
+If the [.model.ts](../reference/file-types/model-ts) file cannot
+be parsed by the model editor, the field shown here will be read only
+and you will need to make changes only in the **Source** tab. This can
+happen if the .model.ts contains more complex code.
+
+## Wordlist tabs
+
+Each [wordlist file](../reference/file-types/tsv) referenced in the
+Wordlists grid is given its own tab. Wordlist tabs have two views:
+Design, and Code. Changes to one view are reflected immedaitely in the
+other view. [Learn more about wordlists](../guides/lexical-models/tutorial/step-3).
+
+Wordlist files should be stored in UTF-8 encoding (preferably without
+BOM), and tab-separated format. Some applications, such as Microsoft
+Excel, are unable to export in this format, so the compiler will also
+accept UTF-16 encoded files, but this encoding is not recommended as it
+is more difficult to manage in version control systems such as Git,
+which is the system used by the Keyman Cloud lexical model repository.
+
+![Model Editor - Wordlist tab - Design view](../images/ui/frmModelEditor_Wordlist_Design.png)
+
+Every line of the tab-separated format file is shown here, and can be
+edited directly. For most wordlists, it will be more effective to use an
+external dictionary tool, such as SIL Fieldworks or SIL PrimerPrep to
+generate the wordlist from a text corpus, and use this tab just to
+preview the contents of the file.
+
+The **Sort by frequency** button has no effect on the functioning of the
+wordlist, but can help you, the editor, by showing more common words
+earlier in the list.
+
+![Model Editor - Wordlist tab - Code view](../images/ui/frmModelEditor_Wordlist_Code.png)
+
+> ### Note
+The wordlist Code editor will accept the Tab key to insert a Tab
+character. In other code editors in Keyman Developer, the Tab key
+inserts spaces.
+
+## Source tab
+
+![Model Editor - Source tab](../images/ui/frmModelEditor_Source.png)
+
+This tab shows the source view of the model, written in the Typescript
+programming language. The format of this file is documented in the
+[Lexical Model File Format Reference](../reference/file-types/model-ts).
+
+## Build tab
+
+![Model Editor - Build tab](../images/ui/frmModelEditor_Build.png)
+
+A set of common controls is across the top of this tab:
+
+Compile Model
+: You can compile the model for testing and installation by clicking
+ the **Compile Model** button, or
+ selecting Model, Compile Model. This will compile the model into a
+ single Javascript file, named as shown in the **Target filename**
+ field.
+
+Open source, build, and project folder
+: Opens the folder which contains the model source, build, and project files.
+
+
+
+### Test Lexical Model in Web Browser
+
+Before distributing the lexical model, you can test it in a web browser.
+[Learn more about testing lexical models](../guides/test/lexical-model).
+
+Keyboard for testing
+: Allows you to select a compiled keyboard to provide input for
+ testing the lexical model. You can also choose to start testing a
+ keyboard directly from the keyboard source file and it will then be
+ available for use when testing the lexical model, until you restart
+ Keyman Developer. The keyboard must be a .js format keyboard with a
+ touch keyboard.
+
+Test Lexical Model
+: Starts the Keyman Developer Web Server for the current lexical
+ model. This will list the various IP addresses and hostnames that Keyman Developer
+ is listening on
+
+Configure...
+: Pressing this allows the [Options](./options) dialog to pop-up.
+
+Open in browser
+: Starts your default browser with the selected address to allow
+ testing of the model directly
+
+Send to email...
+: Sends the list of web addresses as an email so that you can more
+ easily open the test site on your mobile device, or share with
+ someone else on your network.
+
+Copy link
+: Once the IP addresses are shown, feel free to copy any one of the links.
+
+To test on other platforms, open one of the debug host addresses listed
+on your device. Your device will need to be on the same network as the
+Keyman Developer computer.
+
+There is also a QR code which you can scan with your mobile device
+camera to automatically load the selected site in the list of debug web
+addresses. This is the quickest way to start testing on your mobile
+device.
+
+The Send to email function is another way to open the addresses on your
+mobile device without having to enter them manually.
+
+Learn more about [testing on the web and mobile devices](../guides/test/lexical-model).
\ No newline at end of file
diff --git a/developer/docs/help/context/new-ldml-project-parameters.md b/developer/docs/help/context/new-ldml-project-parameters.md
new file mode 100644
index 00000000000..c1bf7cabb6c
--- /dev/null
+++ b/developer/docs/help/context/new-ldml-project-parameters.md
@@ -0,0 +1,63 @@
+---
+title: New LDML Keyboard Project Parameters Dialog
+---
+
+Allows you to quickly fill in common parameters for a new LDML keyboard project, adding keyboard, package, documentation and metadata files, following the [file layout](/developer/keyboards/) used in the [Keyman keyboards repository](https://github.com/keymanapp/keyboards).
+
+Projects can also be created from the command line with [KMConvert](kmconvert).
+
+![Keyboard Editor - New file, Details tab](../images/ui/frmNewLDMLProjectParameters.png)
+
+### Parameters
+
+Keyboard Name
+
+: **Required.** The descriptive name of the keyboard. This will be set in the
+ `
+
+Proxy Settings...
+: Configure the HTTP proxy settings for online functionality within
+ Keyman Developer, such as uploading files to Tavultesoft.
+
+SMTP Settings...
+: Configure your SMTP server settings for email functionality within
+ Keyman Developer, such as emailing debug URLs for the touch layout
+ debugger.
+
+## Editor tab
+
+![Options dialog - Editor tab](../images/ui/frmOptions_Editor.png)
+
+Use tab character
+: Sets whether to use the Tab character or spaces for indents.
+
+Indent size
+: The indent size, measured in number of spaces.
+
+Link quoted font size to primary font size
+: If checked, the quoted font size will be the same as the default
+ font size.
+
+Default font
+: Sets the font for ordinary text in the editor.
+
+Quoted font
+: Sets the font for displaying comments text and strings in Keyman
+ source files in single or double quotes.
+
+Editor theme
+: Sets the display theme and syntax highlighting options for the
+ editor. A [custom theme](../reference/editor-themes) can be defined
+ in a JSON file.
+
+## Debugger tab
+
+![Options dialog - Debugger tab](../images/ui/frmOptions_Debugger.png)
+
+Enable test window (from version 5.0)
+: Enables the Keyman 5-style Test window instead of the debugger.
+
+Breakpoints fire also when exiting line
+: Breakpoints will fire when a rule has finished being processed also.
+
+Turn on single step after breakpoint
+: Activates single-step mode after a breakpoint fires.
+
+Show matched character offsets in stores
+: Displays numeric indices of characters in stores
+
+Automatically recompile if no debug information available
+: When the debugger starts, rebuild a keyboard without prompting if
+ the keyboard has no debug symbols included.
+
+Automatically reset debugger before recompiling
+: If you are debugging a keyboard, make a change to the keyboard, and recompile it,
+ then the debugger needs to be reset in order to get access to the new keyboard.
+ Keyman Developer will normally prompt you to do this, but if you set this option,
+ then it will reset the debugger automatically instead.
+
+## Character Map tab
+
+![Options dialog - Character Map tab](../images/ui/frmOptions_CharacterMap.png)
+
+Find character under cursor automatically
+: The character beneath the cursor in the edit window will be
+ highlighted in the Character Map. This will intelligently parse the
+ character data under the cursor so that a character code (e.g.
+ U+1234) will be highlighted correctly.
+
+Disable database lookups
+: Disables looking characters up in the database.
+
+Update database
+: Rebuilds the Unicode character database from source unicodedata.txt
+ and blocks.txt. These files can be downloaded from the Unicode
+ website at
+ [http://www.unicode.org/ucd/](http://www.unicode.org/ucd/).
+ This lets you update the character map with a newer version of Unicode.
+ Keyman Developer 17.0 was released with Unicode version 15.1 data.
+
+## Server tab
+
+![Options dialog - Server tab](../images/ui/frmOptions_Server.png)
+
+There are two configurations in the Keyman Developer Server section:
+1. Configure Server...
+2. List local URLs for Server
+
+Configure Server...
+
+: After clicking the button, a new window will pop-up
+
+ ![Keyman Developer Server Options dialog](../images/ui/frmKeyman_Developer_Server_Options.png)
+
+ You can customize the Keyman Developer server for testing the usability of your keyboard. Features of the Keyman Developer server include:
+
+ * Seamless integration with the IDE (Start, Stop, Live reload, Recompile...)
+ * Port configuration
+ * Allow testing cross devices
+ * Wider range support for NGROK...
+
+ [Find out more](https://github.com/keymanapp/keyman/pull/6073)
+
+ Understanding the three tick boxes will enhance the usability of Keyman Developer:
+ - **Leave Server running after closing IDE**: allows keyboard testing on local URLs after closing Keyman Developer.
+ - **Use ngrok to provide public url for web debugger**: configure NGROK as instructed, once it looks something like this:
+ ![Keyman Developer NGROK](../images/ui/frmNGROK_Config.png)
+
+ Please restart the machine, and the URL (*link ending in .ngrok-free.app*) will appear in the specified locations:
+ ![Keyman Developer NGROK URL 1](../images/ui/frmNGROK_Server_1.png)
+ ![Keyman Developer NGROK URL 2](../images/ui/frmNGROK_Server_2.png)
+
+ Now, freely share it across multiple devices using the link or QR code.
+ ![Keyboard Test on Mobile](../images/ui/NGROK_Phone.png)
+
+ - **Show server console window on start**: once ticked, Keyman Developer's server console will appear as an independent window as a node application with the default message:
+ `Starting Keyman Developer Server 17.0.290-beta, listening on port 8008.`
+
+List local URLs for Server
+: Below Configure Server, untick the box, and it will quit displaying any local URLs for keyboard testing.
+ ![Keyman Developer Server Options dialog](../images/ui/frmList_Local_URLs.png)
+
+Explore more: [Keyman Developer Server](server#toc-configuring-keyman-developer-server)
diff --git a/developer/docs/help/context/package-editor.md b/developer/docs/help/context/package-editor.md
new file mode 100644
index 00000000000..38026732bc7
--- /dev/null
+++ b/developer/docs/help/context/package-editor.md
@@ -0,0 +1,194 @@
+---
+title: Package Editor
+---
+
+The Package Editor allows you to edit a .kps package source file, which will be
+compiled into a .kmp package file. These files contain a set of keyboards,
+documentation, fonts, and other related files, which make distribution and
+installation of a keyboard in Keyman simple.
+
+## Files tab
+
+![Package Editor - Files tab](../images/ui/frmPackageEditor_Files.png)
+
+Use the Add... button to select the files to be included in the package, such
+as .kmx and .js keyboard files, documentation, and related fonts; but you can
+include any type of file here as well.
+
+If you add another .kmp package file, it will also be installed when this
+package is installed, but will be managed separately and uninstalled separately.
+
+## Keyboards tab
+
+![Package Editor - Keyboards tab](../images/ui/frmPackageEditor_Keyboards.png)
+
+The Keyboards tab shows you some status information of keyboards you've added to
+the package. There are also optional font dropdowns and a required "Languages"
+section. The fields on the tab are:
+
+**Description**
+
+: The keyboard name (`&name` system store).
+
+**Files**
+
+: This shows the associated keyboard files (.kmx and .js files)
+
+**Version**
+
+: The keyboard version (`&keyboardversion` system store).
+
+**Is right-to-left**
+
+: Currently, this is only applicable to Android, iOS and Web (that is, .js
+ format) keyboards. This reflects the checkbox in the [Details tab](./keyboard-editor#details-fields) of the Keyboard editor.
+
+> ### Note
+ **Is right-to-left** will only be true if the following occur:
+ * "Keyboard is right-to-left" is checked in the Keyboard editor
+ * The compiled .js keyboard is added to the package
+
+**Keyboard font**
+
+: When font files are added to the package, this dropdown tells the Keyman apps
+ which font to use when rendering the On Screen Keyboard touch keyboard.
+ Optional.
+
+**Display font**
+
+: When font files are added to the package, this dropdown tells the Keyman apps
+ for iOS and Android which font to use in edit fields. It only applies within
+ the Keyman app and apps that support this functionality. Optional.
+
+**Web fonts**
+
+: It is possible now to specify additional font filenames that will be made
+ available to KeymanWeb, for example, providing fonts in WOFF or WOFF2 formats.
+ [More on web fonts](select-web-fonts)
+
+**Languages**
+
+: Because the language information in the .kmn source is deprecated, the
+ "Languages" section is required. Each language listed here is a [BCP 47 language tag](../reference/bcp-47). Use the **Add** button to
+ bring up the "Select BCP 47 Tag" dialog. When Keyman installs the keyboard
+ package, it will associate the keyboard with the language(s) you select here.
+ Required.
+
+ ![Package Editor - Select BCP 47 Tag dialog](../images/ui/frmPackageEditor_Select_BCP_47_Tag.png)
+
+**Examples**
+
+: Examples allow you to provide short keystroke sequences to type sample text
+ with your keyboard. This can be the easiest way for a new user to start using
+ your keyboard, and is particularly helpful when your keyboard makes use of
+ keying sequences that may not be immediately obvious.
+
+ As the keying examples may vary by language, you should include the BCP 47 tag
+ for each example, plus a short description (in English) of what the text is
+ about.
+
+ The key sequence must list each key combination, separated by space. The
+ actual text for each key is reasonably arbitrary, to allow you to provide
+ examples for touch keyboards as well as desktop keyboards. There are three
+ special kinds of key strings:
+ * Modifier keys may be specified with the `+` character, e.g. `shift+e` or `right-alt+k`. Use lower case keys. The suggested standard modifiers are: `shift`, `ctrl`, `alt`, `left-alt`, `right-alt`, `left-ctrl`, `right-ctrl`, and `option` (mac).
+ * The space key itself may be specified with `space`.
+ * To avoid confusion with modifier keys, the + key can be specified with `plus`.
+
+ For example, the key sequence x , j , m , Shift +e , r may be specified as `x j m shift+e r` or `x j m E r`.
+
+ ![Package Editor - Edit Example dialog](../images/ui/frmPackageEditor_EditExample.png)
+
+## Details tab
+
+![Package Editor - Details tab](../images/ui/frmPackageEditor_Details.png)
+
+Enter the name of the package into the Package Name field; this will be
+automatically filled from the name of the first keyboard you add to the package.
+
+Select a HTML readme file if you have one to include in the package; this will
+be displayed before the package is installed as information about the package.
+
+Version numbers should be in the form `major.minor[.patch]`. Patch is optional
+but is helpful for small bug fix releases. Each of the sections of the version
+should be an integer. Keyman Desktop does integer comparisons on the version
+number components, so, for example, version 2.04 is regarded as newer than
+version 2.1. Alphabetic or date formats should be avoided as the installer for
+the keyboard cannot determine which version is older reliably.
+
+You can also tick the checkbox labelled "Package version follows keyboard
+version" to have the package version automatically track the keyboard version.
+
+The Copyright indicates the overall copyright of the package and all its
+contents.
+
+Fill in the individual or organisation who authored the package in the Author
+field, and a contact email address into the E-mail address field. These fields
+are optional.
+
+A web site is encouraged and should be filled in the web site field, including
+the initial "http://" or "https://".
+
+A package can optionally include a 140x250 JPEG or PNG image file to be shown
+during installation. This image file must be added to the Files list in step 1,
+then selected from the list here.
+
+### Related packages
+
+If a keyboard package is intended to replace an existing keyboard, or if there
+are related packages, then the identifiers for these packages should be listed
+here.
+
+There are two types of package relationships:
+
+* Deprecation: if the package replaces an existing package, ensure that you
+ select the 'Deprecated' check box. This information will be used on the Keyman
+ website, both to help users select the most recent version of a keyboard, and
+ also to offer upgrades to new packages for existing users of the package.
+
+* Related: if the 'Deprecated' check box is not selected, then this the
+ relationship information is used to provide links to the related packages on
+ the Keyman website. For example, you may create two keyboards for the same
+ language with different keying orders; then it would be appropriate to
+ cross-reference them with this field.
+
+![Package Editor - Edit Related Packages dialog](../images/ui/frmPackageEditor_EditRelatedPackage.png)
+
+## Shortcuts tab
+
+![Package Editor - Shortcuts tab](../images/ui/frmPackageEditor_Shortcuts.png)
+
+You can optionally have the package create a Start Menu folder, and populate it
+with shortcuts to the files in the package, and optionally also a shortcut for
+uninstallation.
+
+To add an entry, click New, and set the description, and choose a program or
+document to load.
+
+An uninstall shortcut is no longer recommended; uninstall should be managed by
+the user in Keyman Configuration.
+
+## Source tab
+
+![Package Editor - Source tab](../images/ui/frmPackageEditor_Source.png)
+
+The source of the .kps file in XML format. All details from previous tabs can be
+seen in the Source tab, and changes in either the Source tab or the other tabs
+will be reflected immediately in the other.
+
+## Compile tab
+
+![Package Editor - Compile tab](../images/ui/frmPackageEditor_Build.png)
+
+The final step is building the package. You must save the package file before
+building, then choose Compile Package to build the package file. You can then
+test the package to verify that it will install correctly with the Install
+Package button.
+
+Once you are satisfied with your package, you should consider submitting the
+keyboard to the [Keyman Cloud Keyboards Repository](/developer/keyboards/).
+
+## Package Installers
+
+As of Keyman Developer 17, bundled executable package installers for Keyman for
+Windows can be created using [kmc](kmc), but cannot be created within the IDE.
\ No newline at end of file
diff --git a/developer/docs/help/context/project.md b/developer/docs/help/context/project.md
new file mode 100644
index 00000000000..84bfb38722c
--- /dev/null
+++ b/developer/docs/help/context/project.md
@@ -0,0 +1,36 @@
+---
+title: Project Window
+---
+
+The Project allows you to manage all the files that you are working on in Keyman Developer, and guides you on the steps to creating a keyboard solution. Changes to the project are automatically saved.
+
+You can open the Project window by pressing Ctrl +Shift +P , or selecting View, Project.
+
+## Welcome
+
+![Project window - Welcome tab](../images/ui/frmProject_Welcome.png)
+
+
+This tab lists all the files that you have recently worked on.
+
+## Keyboards
+
+![Project window - Keyboards tab](../images/ui/frmProject_Keyboards.png)
+
+
+This tab lists all the keyboard source files in your project.
+
+## Packaging
+
+![Project window - Packaging tab](../images/ui/frmProject_Packaging.png)
+
+
+This tab lists all the package source files in your project.
+
+## Distribution
+
+![Project window - Distribution tab](../images/ui/frmProject_Distribution.png)
+
+
+This tab lists all other files in your project, such as the
+[.keyboard_info file used for uploading to the Keyboards repository](/developer/keyboards/).
\ No newline at end of file
diff --git a/developer/docs/help/context/select-web-fonts.md b/developer/docs/help/context/select-web-fonts.md
new file mode 100644
index 00000000000..f03d437faf0
--- /dev/null
+++ b/developer/docs/help/context/select-web-fonts.md
@@ -0,0 +1,17 @@
+---
+title: Package Editor - Select Web Fonts
+---
+
+The Select Web Fonts dialog allows you to choose additional fonts that will be
+made available as suggested fonts for KeymanWeb. These fonts should be included
+in the package.
+
+Even though the fonts are included in the package, KeymanWeb will source the
+fonts from its own infrastructure, so they will need to be shared in the Keyman
+content delivery network repository
+[s.keyman.com](https://github.com/keymanapp/s.keyman.com).
+
+Separate fonts can be specified for the On-Screen Keyboard and the recommended
+Display font.
+
+![Select Web Fonts dialog](../images/ui/frmPackageEditor_SelectWebFonts.png)
\ No newline at end of file
diff --git a/developer/docs/help/context/server.md b/developer/docs/help/context/server.md
new file mode 100644
index 00000000000..0a04d425d32
--- /dev/null
+++ b/developer/docs/help/context/server.md
@@ -0,0 +1,398 @@
+---
+title: Keyman Developer Server
+---
+
+# Introduction
+
+Keyman Developer Server provides a web-based front-end for testing and sharing
+keyboards, lexical models and packages.
+
+The primary benefits of Keyman Developer Server are:
+
+* Test keyboards in a browser on your computer, including simulation of mobile
+ devices and touch keyboards
+* Test lexical models in a browser on your computer
+* Test keyboards, lexical models and packages on mobile devices
+* Share keyboards, lexical models and packages to mobile devices and other users
+
+Keyman Developer Server was added in Keyman Developer 15, and replaces the old web
+Test feature.
+
+![](images/server-iphone.png)
+
+# Installation and Startup
+
+## Windows
+
+Keyman Developer Server is always installed as a part of Keyman Developer.
+
+When you start Keyman Developer, Keyman Developer Server will start in the
+background.
+
+> **Note:** The first time Keyman Developer Server starts, you may be presented
+with a warning from your firewall or security software about Node.js Javascript
+Runtime running from the Keyman Developer folder. This is the Keyman Developer
+Server host process, and you should choose to allow Keyman Developer Server to
+communicate at least on your local private network, or as your circumstances
+dictate.
+
+By default, Keyman Developer Server will run when Keyman Developer is running,
+and will shutdown when Keyman Developer is closed. However, you can also
+configure Keyman Developer Server to stay running even after Keyman Developer is
+closed.
+
+An icon will be displayed in the system notification area in the Taskbar
+(it may be hidden in the expansion area):
+
+![](images/taskbar.png)
+
+Clicking on the icon opens a menu:
+
+![](images/taskbar-menu.png)
+
+The following menu items are available:
+
+**Open `http://localhost:8008` in browser**
+
+: Loads Keyman Developer Server home page in your default browser
+
+**Show console**, **Hide console**
+
+: Controls the visibility of the Node console for Keyman Developer Server. You
+ would only need to view the Node console if you were diagnosing issues with
+ Keyman Developer Server
+
+**Exit Keyman Developer Server**
+
+: Shuts down the Server. The Server can be restarted by closing and re-opening
+ Keyman Developer.
+
+## Linux and macOS
+
+Keyman Developer Server is included in the kmcomp.zip standalone distribution.
+It requires nodeJS 16.0 or later, which should be installed according to your
+platform's instructions.
+
+On macOS, if you use Homebrew, you can install nodeJS in Terminal:
+
+```shell
+brew install node
+```
+
+You can download the latest kmcomp.zip with the following commands:
+
+```shell
+mkdir kmcomp
+cd kmcomp
+curl -L https://keyman.com/go/download/kmcomp -o kmcomp.zip
+unzip kmcomp.zip
+```
+
+To start the server, from the kmcomp folder you created earlier, run the
+following command:
+
+```shell
+cd server
+npm run prod
+```
+
+You can stop the server at any time by pressing Ctrl +C
+in the Terminal / shell.
+
+# Using Keyman Developer Server
+
+## Testing a Keyman keyboard in Keyman Developer Server
+
+In Keyman Developer, open a keyboard project, and load the keyboard .kmn file.
+In the Build tab, click **Compile keyboard**, then click **Test keyboard on
+web**. Finally, click **Open in browser** to access the keyboard in your
+local Keyman Developer Server instance.
+
+You will be presented with the default page for Server.
+
+You will need to select your keyboard from the **Keyboard** menu. The keyboard will
+be presented and you can test it either by typing using your hardware keyboard,
+or interacting with the On-Screen Keyboard.
+
+![](images/server-iphone.png)
+
+When you make changes to your keyboard and recompile, the keyboard will be
+reloaded instantly. There is no need to click the **Test keyboard on web**
+button again.
+
+Keyman Developer Server remembers the ten most recent keyboards that you have
+been testing, even after exiting and restarting. This makes it easier to switch
+between projects in Keyman Developer.
+
+## Testing a lexical model in Keyman Developer Server
+
+In Keyman Developer, open a lexical model project, and load the model .model.ts
+file. In the Build tab, click **Compile model**, then click **Test lexical
+model**. Finally, click **Open in browser** to access the model in your local
+Keyman Developer Server instance.
+
+You will be presented with the default page for Server.
+
+You will need to select a keyboard that works with the model from the
+**Keyboard** menu, and also the model you have just started testing from the
+**Model** menu. You will have the best results if you select a mobile device to
+test the model.
+
+![](images/lexical-model-test.png)
+
+When you make changes to your model and recompile, the model will be reloaded
+instantly. There is no need to click the **Test lexical model** button again.
+
+Keyman Developer Server remembers the ten most recent lexical models that you
+have been testing, even after exiting and restarting.
+
+## Sharing a package to test from Keyman Developer
+
+In Keyman Developer, open either a keyboard or a lexical mdoel project, and load
+the .kps file. In the Build tab, click **Compile package**, then click **Test
+package on web**. Finally, click **Open in browser** to access the package in
+your local Keyman Developer Server instance.
+
+The package will be available for install from the hamburger menu at the top
+left of the banner. This is most useful when you access Server from another
+device, as you can then easily install the package into Keyman on that device
+without having to worry about copying files around manually.
+
+![](images/hamburger-menu.png)
+
+Keyman Developer Server remembers the ten most recent packages that you have
+been testing, even after exiting and restarting.
+
+## Keyman Developer Server interface
+
+![](images/server-iphone.png)
+
+This describes the various elements of the Keyman Developer Server web interface.
+
+### Banner
+
+![](images/banner.png)
+
+On desktop devices, the top banner of Server includes four menus. On a mobile
+device, the Device menu will not be visible, as the keyboard view will always be
+the touch keyboard view at the bottom of the screen.
+
+### Status Bar
+
+![](images/status-bar.png)
+
+Below the banner, you will find a smaller status bar showing the currently
+selected keyboard, model, and device.
+
+### Character Grid
+
+![](images/character-grid.png)
+
+Under this is a character preview grid, which shows you the underlying Unicode
+values of the characters in your text area. This character grid also shows the
+current cursor position and highlighted text, so you can easily correlate the
+shaped characters with their underlying representation in Unicode.
+
+The example image above shows a Khmer keyboard with a sample string saying
+"Khmer Language". Observe how the character preview grid shows the unshaped
+underlying characters, how the order of characters stored in the text content
+may differ from the presentation, and how diacritics and joined marks are
+indicated with dotted circles.
+
+### Text Area
+
+![](images/text-area.png)
+
+The text area is immediately below this. You can enter text into this text area
+using your hardware keyboard or using the On-Screen Keyboard.
+
+### On-Screen Keyboard
+
+![](images/on-screen-keyboard.png)
+
+The On-Screen Keyboard, shown directly below, is fully interactive, including
+support for longpress and other gestures on touch, using your mouse on a
+desktop device. The layout of the On Screen Keyboard and the device frame are
+controlled from the **Device** menu.
+
+### Hamburger menu (three horizontal lines)
+
+![](images/hamburger-menu.png)
+
+**Download and install Keyman**
+
+: This is a shortcut to install Keyman for the current device. It detects
+ the type of device you are viewing this page on, and provides a direct
+ link to the install, whether that be a Windows executable, a Play Store
+ link, an App Store link or other method.
+
+**List of packages**
+
+: A list of up to ten most recent packages that you have been testing in
+ Keyman Developer. This will open the package in Keyman on the device, which
+ is most useful when testing on a mobile device or sharing the package to
+ other computers.
+
+**Upload file...**
+
+: Allows you to manually upload any compiled .js keyboard, compiled .model.js
+ lexical model, compiled .kmp file, or .ttf font to be available for testing.
+ This will be most useful when running Server on macOS or Linux, where
+ integration with the Keyman Developer IDE is not available.
+
+ You can also drag and drop these same files onto the page.
+
+**Help...**
+
+: Opens this help site.
+
+**About Keyman Developer Server**
+
+: Provides information about the version of Keyman Developer Server.
+
+### Keyboard menu
+
+![](images/keyboard-menu.png)
+
+Lists up to ten keyboards that you have been testing most recently from Keyman
+Developer. Select a keyboard to start testing it.
+
+If you select **(system keyboard)**, this disables testing of Keyman keyboards.
+
+### Model menu
+
+![](images/model-menu.png)
+
+Lists up to ten lexical models that you have been testing most recently from
+Keyman Developer. Select a model to start testing it. Note that the
+model/keyboard correlation is controlled by you within this view -- language
+association is not available in Keyman Developer Server.
+
+While the desktop keyboard views will show predictions if a model is enabled,
+this is not currently fully supported outside of Keyman Developer Server. You
+will have better results selecting a touch device in the Device menu for testing
+lexical models.
+
+If you select **(no model)**, this hides the predictions banner.
+
+### Device menu
+
+![](images/device-menu.png)
+
+This function is available only from desktop / laptop computers. On mobile
+phones and tablets, the Device menu is hidden and only the device's default
+Keyman keyboard form factor is available.
+
+When available, this allows you to simulate use of the devices shown, letting
+you test the keyboard and/or model as you would see it and use it on that
+device. The device selected changes the presentation of the keyboard, and is
+reflected in the [`platform()` statement](/developer/language/reference/platform)
+context as well.
+
+The devices listed are representative of the various types of platforms that
+Keyman supports, to allow testing on each of the available form factors.
+
+## Making your Keyman Developer Server instance globally available with ngrok
+
+One of the fiddliest parts of testing keyboards and models with mobile devices
+is transferring the files to your phone or tablet. Keyman Developer Server will
+make files available on your local network for access, but it can be difficult
+to configure firewalls and network settings successfully.
+
+To simplify testing, Keyman Developer Server integrates with a free tool called
+[ngrok](https://ngrok.com) that provides a public URL to your instance of Keyman
+Developer Server. This URL is dynamic and can change over time, and is only
+available while Keyman Developer Server is running. This URL can be accessed
+from any mobile device that is online, whether or not it is on your local
+network. This means you can also quickly share keyboards, packages, and models
+with other users, with a minimum of difficulty.
+
+**Warning:** While the ngrok service is running, your computer will be sharing
+your Keyman Developer Server instance to anyone in the world. The Server
+instance has read-only access to the keyboards that you have loaded into it, but
+you may wish to turn ngrok support on only when you need it.
+
+ngrok does need a little bit of configuration. These steps will take you through
+the process on Windows; on macOS and Linux, you will need to download and
+configure ngrok manually.
+
+1. In Keyman Developer, open **Tools | Options**, select **Server** tab, and
+ click **Configure Server...**. You should be presented with a window similar
+ to this:
+
+ ![](images/config.png)
+
+2. Tick the box **Use ngrok to provide public url for Server**
+3. If ngrok is available, a version number will be shown to the right of the
+ **Download or update ngrok** button. Otherwise, click this button to download
+ the latest version.
+4. You'll need a free ngrok account, so click the **Create free ngrok
+ account...** button to open the ngrok website, and follow the steps there
+ to finish the account creation process.
+5. After creating the account, you should be presented with an authentication
+ token. You can also access this by clicking the **Get token** link in the
+ Keyman Developer Server Options dialog box. Paste this token into the
+ **Authentication token** edit box.
+6. Select the most appropriate region for your location, to minimize the latency
+ (delay) when using the ngrok service.
+7. Click **OK** to save changes, and start the ngrok service within Keyman
+ Developer Server.
+
+Once ngrok is configured, you should see an additional **Server** menu item in
+Keyman Developer's **Tools** menu, with three items:
+
+**Open `https://some-url.ngrok.io` in browser**
+
+: Opens the public URL to your current Keyman Developer Server instance in the
+ default browser on your computer.
+
+**Copy URL to clipboard**
+
+: Copies the public URL to the clipboard so you can share it via email, instant
+ message, or other medium.
+
+**Configure...**
+
+: Opens the Keyman Developer Server Options dialog box.
+
+# Configuring Keyman Developer Server
+
+Keyman Developer Server has a number of configuration options. On Windows, using
+Keyman Developer is the easiest way to manage these options. Open **Tools |
+Options**, select **Server** tab, and click **Configure Server...**.
+
+![](images/config.png)
+
+## Configuration Files
+
+The configuration file `config.json` is stored in the following locations:
+* Windows: `%appdata%\Keyman\Keyman Developer\Server\config.json`
+* Linux: `~/.local/share/Keyman/Keyman Developer/Server/config.json`
+* macOS: `~/Library/Preferences/Keyman/Keyman Developer/Server/config.json`
+
+## Configuration settings
+
+The value in parentheses is the name of the setting in `config.json`, where present.
+
+* **Default port** (`port`): specifies the port that Server listens on (http).
+ Defaults to `8008`.
+* **Leave Server running after closing IDE**. This is off by default. If set,
+ Keyman Developer Server will start with Keyman Developer, and run until the
+ Windows session is finished by logging off or shutting down the computer. Keyman
+ Developer Server can be manually closed from the system notification area icon.
+* **ngrok Settings**
+ * **Use ngrok to provide public url for Server** (`useNgrok`): Defaults to `false`.
+ Set to `true` if you want to enable use of ngrok tunnels.
+ * **Authentication token** (`ngrokToken`): the token provided by ngrok for setting
+ up a tunnel.
+ * **Region** (`ngrokRegion`): select the region in which you are located in order
+ to minimize latency through the tunnel.
+* **Show Server console window on start**. This is off by default, and would only
+ normally be used for diagnostics or development of Keyman Developer itself.
+
+# Related Topics
+
+* [Keyboard Editor](keyboard-editor)
+* [Model Editor](model-editor)
+* [Package Editor](package-editor)
+* [Options](options)
diff --git a/developer/docs/help/guides/command-line.md b/developer/docs/help/guides/command-line.md
new file mode 100644
index 00000000000..f4820281ce4
--- /dev/null
+++ b/developer/docs/help/guides/command-line.md
@@ -0,0 +1,9 @@
+---
+title: Keyman Developer command line tools guide
+---
+
+Keyman Developer has a set of command line tools for creating keyboards and
+models. For more details see:
+
+* [`kmc` -- the Keyman command line compiler](../reference/kmc/)
+
diff --git a/developer/docs/help/guides/develop/advanced-keyboard-development-example.md b/developer/docs/help/guides/develop/advanced-keyboard-development-example.md
new file mode 100644
index 00000000000..204d031a6a1
--- /dev/null
+++ b/developer/docs/help/guides/develop/advanced-keyboard-development-example.md
@@ -0,0 +1,236 @@
+---
+title: Advanced Keyboard Development Example
+---
+
+## Introduction
+
+Whenever you want to do much more in a keyboard than simple character
+substitution, you will generally need to make use of advanced features
+such as **stores**, **deadkeys**, and **multiple groups**. The [tutorial](tutorial)
+has already shown some basic usage of stores and deadkeys, but has not
+covered other possibilities of their use. In this topic we will examine
+a keyboard that demonstrates some other uses of stores and deadkeys, and
+introduces the use of multiple groups for complex processing.
+
+We recommend that you read the topics on
+[groups](/developer/language/guide/groups),
+[stores](/developer/language/guide/stores), and
+[deadkeys](/developer/language/reference/deadkey) before continuing.
+
+The keyboard we will examine is **IPAMenu.kmn** (found in the Keyman
+Developer Samples folder), which contains the beginnings of a menu-based
+keyboard for using the International Phonetic Alphabet (IPA). You will
+need a Unicode font with IPA characters (such as **Gentium**) to
+properly use this keyboard, but you should be able to follow the code
+even without. For more information about the IPA, see [The International
+Phonetic
+Association](https://www.internationalphoneticassociation.org/).
+
+Most of the IPA glyphs are derived from glyphs in the Latin alphabet,
+used to represent differing sounds used in language. Because of this, it
+seemed reasonable to place all glyphs derived from "a" on the "a" key,
+and so on. We have implemented a few of the vowel symbols in this keyboard:
+
+![](../../images/ipa.gif)
+
+
+## Overview of the Keyboard
+
+The basic operation of the keyboard is the displaying of a menu when a
+key is pressed, followed by the output of a single character when the
+user makes a selection from that menu with a number key.
+
+The first thing to notice is the organisation of most of the input and
+output into stores:
+
+```keyman
+store( choices ) '1234567890'
+store( a_menu ) '[1æ 2a 3ɑ 4ɐ 5ʌ 6ɒ]'
+store( e_menu ) '[1ɛ 2ɜ 3ə 4e 5ɘ 6ɚ 7ɝ 8ɞ]'
+store( o_menu ) '[1o 2ø 3ɔ]'
+store( a_chars ) 'æaɑɐʌɒ' dk(a_err) dk(a_err) dk(a_err) dk(a_err)
+store( e_chars ) 'ɛɜəeɘɚɝɞ' dk(e_err) dk(e_err)
+store( o_chars ) 'oøɔ' dk(o_err) dk(o_err) dk(o_err) dk(o_err) dk(o_err) dk(o_err) dk(o_err)
+```
+
+An important point to notice is the use of deadkeys in these stores:
+we'll explain their purpose here later.
+
+The next thing that stands out is that the file has six separate groups,
+four of which handle keystrokes and two which manipulate context only:
+
+```keyman
+group( first )
+
+group( main ) using keys
+
+group( final )
+
+group( a_group ) using keys
+
+group( e_group ) using keys
+
+group( o_group ) using keys
+```
+
+## Full source
+
+The full keyboard source is shown below. Refer to this when following
+the description after.
+
+```keyman
+c IPAMenu.kmn
+c
+c Copyright ©2002 Tavultesoft.
+c
+c Demonstrates simple use of multiple groups to create a menu-based
+c system for entering IPA characters, based on an example created
+c by Peter E. Hauer's.
+c
+c A font with the IPA unicode characters defined must be used with this
+c keyboard, for example, Code2000 or Lucida Sans Unicode
+c
+c Note that the stores pertaining to each letter could be placed in the
+c group for that letter; the location of stores in the keyboard source
+c has no effect on the final keyboard.
+
+Version 6.0
+Name "IPA Menu Example"
+
+c This keyboard should be independent of the user's
+c system keyboard layout
+store(&MnemonicLayout) '1'
+
+c *******************************************************************
+begin Unicode > use( first )
+
+c keys used to choose menu items
+store( choices ) '1234567890'
+
+c menu stores
+store( a_menu ) '[1æ 2a 3ɑ 4ɐ 5ʌ 6ɒ]'
+store( e_menu ) '[1ɛ 2ɜ 3ə 4e 5ɘ 6ɚ 7ɝ 8ɞ]'
+store( o_menu ) '[1o 2ø 3ɔ]'
+c add more menu stores here
+
+c character choice stores - error deadkeys are used to pad
+c out the stores so they are the same length as the choices store
+store( a_chars ) 'æaɑɐʌɒ' dk(a_err) dk(a_err) dk(a_err) dk(a_err)
+store( e_chars ) 'ɛɜəeɘɚɝɞ' dk(e_err) dk(e_err)
+store( o_chars ) 'oøɔ' dk(o_err) dk(o_err) dk(o_err) dk(o_err) dk(o_err) dk(o_err) dk(o_err)
+c add more char stores here
+
+c *******************************************************************
+c first matches a menu in the context or passes processing to main
+group( first )
+ c match a menu on the context
+ outs(a_menu) > use(a_group)
+ outs(e_menu) > use(e_group)
+ outs(o_menu) > use(o_group)
+ c add more menus here
+
+ c no menu was in the context, so process keys normally
+ nomatch > use(main)
+
+c *******************************************************************
+c main outputs the menus, and handles any other normal key processing
+group( main ) using keys
+ c output a menu if appropriate
+ + 'a' > outs(a_menu)
+ + 'e' > outs(e_menu)
+ + 'o' > outs(o_menu)
+ c add more menu keys here
+
+c *******************************************************************
+c final matches error markers and finishes processing
+group( final )
+ dk(a_err) > beep outs(a_menu)
+ dk(e_err) > beep outs(e_menu)
+ dk(o_err) > beep outs(o_menu)
+ c add more error-marker handlers here
+
+c *******************************************************************
+c a_group handles the menu for a
+group( a_group ) using keys
+ + any(choices) > index(a_chars, 1) use( final ) c output chosen character
+ + [K_BKSP] > nul c delete menu
+ nomatch > dk(a_err) use( final ) c invalid choice - error
+
+c *******************************************************************
+c e_group handles the menu for e
+group( e_group ) using keys
+ + any(choices) > index(e_chars, 1) use( final ) c output chosen character
+ + [K_BKSP] > nul c delete menu
+ nomatch > dk(e_err) use( final ) c invalid choice - error
+
+c *******************************************************************
+c o_group handles the menu for o
+group( o_group ) using keys
+ + any(choices) > index(o_chars, 1) use( final ) c output chosen character
+ + [K_BKSP] > nul c delete menu
+ nomatch > dk(o_err) use( final ) c invalid choice - error
+
+c add more menu groups here
+
+c End of file
+```
+
+## Primary Operation of the Keyboard
+
+When a key is pressed, execution begins at the group indicated by the
+`begin` statement, in this case the `first` group. Because this group
+does not specify `using keys`, it is limited to context manipulation
+only: the output of this rule is dependent only on what came before the
+current keystroke, and becomes the context for any further groups that
+are called from this one.
+
+Let's suppose the a key has been pressed with no context. The `first` group will have nothing to match on, so the
+`nomatch` rule fires and passes control to the `main` group. Here the
+"a" key is matched, and the `a_menu` store is output, displaying the
+menu of a-like characters.
+
+Now the user is presented with a menu of options to choose from. Suppose
+he types 1 Once again the `first` group gains
+control first, but this time matches the first rule, with the `a_menu`
+string on the context, so control is passed to the `a_menu` group to
+handle the keystroke.
+
+Here the 1 is matched as an entry in the `choices` store, and the corresponding character in the
+`a_chars` store - in this case "æ" - is output. Finally, control goes
+from here to the `final` group, which fails to match anything in the
+context (which now includes the output from the previous group).
+
+## Error Handling
+
+One issue this keyboard has to deal with is if the user tries to select
+an option that's not in the menu - when this happens, it should beep and
+remain at the menu, so the user can try again. Also, if the user wants
+to dismiss the menu, we should allow the use of Backspace to delete it -
+this is simply done with a rule matching `[K_BKSP]` and outputting
+`nul`.
+
+There are two rules which handle the user selecting a nonexistent
+option: if the current menu has fewer than 10 entries, the user can
+press a number key indicating a menu entry that is not there - this
+situation will be matched by the `any(choices)` rule. The other occasion
+is if the user presses any other key which is not valid for selecting a
+menu option. This is handled by the `nomatch` rule in the group. For
+both these cases we want the output to be the same: a `beep`, and remain
+at the menu. To do this we will use deadkeys as error flags, one for
+each menu. By padding the `a_char`, `e_char` and `o_char` stores to 10
+characters with these deadkeys, the output for this first situation will
+be the error flag. Similarly, we can output these deadkeys in the
+`nomatch` rules, to mark an error.
+
+The actual error handling is now done with the `final` group, which
+matches the error flags on the output and outputs the `beep` and the
+appropriate menu again.
+
+## Conclusion
+
+Although this example went nowhere near the limits of what can be done
+with multiple groups, it gave a demonstration of some of the ways
+multiple groups can be made to interact for more powerful processing.
+You should now have some understanding of the use of advanced features
+in Keyman Developer, and be able to begin using them to improve your
+keyboards.
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty.md b/developer/docs/help/guides/develop/creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty.md
new file mode 100644
index 00000000000..21bf08df14b
--- /dev/null
+++ b/developer/docs/help/guides/develop/creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty.md
@@ -0,0 +1,754 @@
+---
+title: Creating a Touch Keyboard Layout Part 2
+---
+
+
+
+
+
+
+This article will continue the guide to creating a touch keyboard layout. In
+particular, we'll look in more detail at how keys are arranged, just what can be
+specified for each key, and lastly, how this all looks in the JSON code used to
+define the long press layout.
+
+## Key and Key Layer Organization
+
+There are two issues that are immediately apparent when considering key layout
+on touch devices.
+
+First, on smaller touch devices, such as phones, if we try to display the same
+arrangement of keys that is used for a typical desktop keyboard, the keys are so
+small that it is difficult to reliably select the wanted key. If used in
+'portrait' view, key widths are too narrow for our 'fat fingers':
+
+![](../../images/touch_amharic_keyboard_4.png)
+
+Or if in 'landscape' view, key heights are too small:
+
+![](../../images/touch_amharic_keyboard_5.png)
+
+The situation is improved markedly if we limit the number of keys per row to ten
+and have no more than four key rows:
+
+![](../../images/touch_amharic_keyboard_6.png)
+
+and, in landscape view:
+
+![](../../images/touch_amharic_keyboard_7.png)
+
+So which keys can be eliminated, and which keys must be on the base (default)
+layer? This brings us to the second point. When using phones and other touch
+layout devices rather than desktop keyboards, text entry is most often
+single-handed, which makes it best to avoid using the 'shift' layer for entering
+normal text. Secondary keyboard layers will, of course, still be usually needed
+for uppercase, numerals and symbols, but will be used much less frequently.
+
+Most desktop keyboards (for European languages, at least) are already laid out
+with no more than ten letter (or digit) keys per row, with the remaining keys
+being used for accented letters, punctuation and other non-letter input. So the
+obvious choice is to move non-letter keys (and accented letters) either to a
+secondary key layer, or to long press ('pop-up') keys. The GFF Amharic desktop
+keyboard is fortunately also arranged with only ten letters per key row (other
+keys being used for punctuation, etc.), as the many different characters in the
+Amharic 'abugida' are generated by multi-letter sequences rather than being
+displayed on separate keys. The Geez script does not have both upper-case and
+lower-case forms, so those initial consonants that require use of the 'shift'
+layer on the desktop keyboard have been added to the corresponding base-layer
+key as long press keys:
+
+![](../../images/touch_amharic_keyboard_8.png)
+
+Arrangement of punctuation and other non-letter keys is more flexible as mobile
+users are generally familiar with using long press keys or a secondary key layer
+for finding and entering digits and special characters. However, some
+punctuation characters are used so frequently that they need to be on the base
+layer. For the GFF Amharic keyboard, the most frequently used punctuation
+characters can be output from the base layer using standard or long press keys.
+The Geez word space character, in particular, is so frequently used that it was
+considered useful to add it to the bottom key, adjacent to the space bar, as is
+sometimes done for other scripts, such as Japanese, on desktop (physical)
+keyboards.
+
+## Arranging keys with the layout editor
+
+The Keyman Developer layout editor really makes it quite easy to try different
+key layouts and choose what is best for your keyboard. The image below
+highlights just how, for any selected key, using the clickable icons circled, a
+key row can be added above (1) or below (2), a key added before (3) or after
+(4), the selected key deleted (5), and how an array of long press keys
+(sometimes called "subkeys") can be added (6).
+
+![](../../images/touch_amharic_keyboard_9.png)
+
+## Key properties
+
+For each visual key, the appearance and behaviour is determined by a number of
+properties:
+
+### Key code
+
+Each key must be given an identifying key code which is unique to the key layer.
+Key codes by and large correspond to the virtual key codes used when creating a
+keyboard program for a desktop keyboard, and should start with `K_`, for keys
+mapped to standard Keyman virtual key names, e.g. `K_HYPHEN`, and `T_` or `U_`
+for user-defined names, e.g. `T_ZZZ`. If keyboard rules exist matching the key
+code in context, then the output from the key will be determined by the
+processing of those rules. It is usually best to include explicit rules to
+manage the output from each key, but if no rules matching the key code are
+included in the keyboard program, and the key code matches the pattern
+`U_xxxx[_yyyy...]` (where `xxxx` and
+`yyyy` are 4 to 6-digit hex strings), then the Unicode characters
+`U+xxxx` and `U+yyyy` will be output. As of Keyman 15, you
+can use more than one Unicode character value in the id (earlier versions
+permitted only one). The key code is always required, and a default code will
+usually be generated automatically by Keyman Developer.
+
+- `K_xxxx` is used for a standard Keyman Desktop key name, e.g.
+ `K_W`, `K_ENTER`. You cannot make up your own `K_xxxx` names.
+ Many of the `K_` ids have overloaded output behaviour, for instance, if no
+ rule is matched for `K_W`, Keyman will output 'w' when it is touched. The
+ standard key names are listed in [Virtual Keys and Virtual Character
+ Keys](/developer/language/guide/virtual-keys "Virtual Keys and Virtual
+Character Keys"). Typically, you would use only the "common" virtual key
+ codes.
+
+- `T_xxxx` is used for any user defined names, e.g. `T_SCHWA`. If you wanted
+ to use it, `T_ENTER` would also be valid. If no rule matches it, the key
+ will have no output behaviour.
+
+- `U_####[_####]` is used as a shortcut for a key that will output those
+ Unicode values, if no rule matches it. This is similar to the overloaded
+ behaviour for `K_` ids. Thus `####` must be valid Unicode characters. E.g.
+ `U_0259` would generate a schwa if no rule matches. It is still valid to
+ have a rule such as `+ [U_0259] > ...`
+
+As noted above, some `K_xxxx` codes emit characters, if no rule is defined.
+There are also some codes which have special functions:
+
+
+
+
+ Identifier
+ Meaning
+
+
+
+
+ `K_ENTER`
+ Submit a form, or add a new line (multi-line); the key action may vary depending on the situation.
+
+
+ `K_BKSP`
+ Delete back a single character. This key, if held down, will repeat. It is the only key code which triggers
+ repeat behavior.
+
+
+ `K_LOPT`
+ Open the language menu (aka Globe key).
+
+
+ `K_ROPT`
+ Hide the on screen keyboard.
+
+
+ `K_TAB`, `K_TABBACK`, `K_TABFWD`
+ Move to next or previous element in a form. Note that these key functions are normally
+ implemented outside the touch layout, so should not typically be used. `K_TAB` will go to previous
+ element if used with the `shift` modifier.
+
+
+
+
+Any key can be used to switch keyboard layers (see
+[`nextlayer`](#toc-nextlayer)), but the following layer-switching key codes have
+been added for switching to some commonly used secondary layers. Note that these
+keys have no specific meaning; you must still set the `nextlayer` property on
+the key.
+
+
+
+
+ Identifier
+ Meaning
+
+
+
+
+ `K_NUMERALS`
+ Switch to a numeric layer
+
+
+ `K_SYMBOLS`
+ Switch to a symbol layer
+
+
+ `K_CURRENCIES`
+ Switch to a currency layer
+
+
+ `K_SHIFTED`
+ Switch to a shift layer
+
+
+ `K_ALTGR`
+ Switch to a right-alt layer (desktop compatibility)
+
+
+
+
+### Key text
+
+The key text is simply the character (or characters) that you want to appear on
+the key cap. This will usually be the same as the characters generated when the
+key is touched, unless contextual rules are used to generate output according to
+a multi-key sequence, as will be true for the GFF Amharic keyboard. Unicode
+characters can be specified either as a string using a target font or using the
+standard hex notation `\uxxxx`. This may be sometimes more convenient, for
+example, for characters from an uninstalled font, or for diacritic characters
+that do not render well alone.
+
+A number of special text labels are recognized as identifying special purpose
+keys, such as Shift, Backspace, Enter, etc., for which icons are more
+appropriately used than a text label. A special font including these icons is
+included with Keyman and automatically embedded and used in any web page using
+Keyman. The list of icons in the font will probably be extended in future, but
+for now the following special labels are recognized:
+
+
+
+
+ Text String
+ Key Cap
+ Key Purpose
+
+
+
+
+ `*Shift*`
+
+ Select Shift layer (inactive). Use on the Shift key to indicate that it switches to the shift layer.
+
+
+ `*Shifted*`
+
+ Select Shift layer (active). Use on the Shift key on the shift layer to switch back to the default layer.
+
+
+ `*ShiftLock*`
+
+ Switch to Caps layer (inactive). Not commonly used; generally double-tap on Shift key is used to access the
+ caps layer.
+
+
+ `*ShiftedLock*`
+
+ Switch to Caps layer (active). Use on the Shift key on the caps layer to switch back to the default layer.
+
+
+
+ `*Enter*`
+ or
+ Return or Enter key (shape determined by writing system direction)
+
+
+ `*LTREnter*`
+
+ Return or Enter key (left-to-right script shape)
+
+
+ `*RTLEnter*`
+
+ Return or Enter key (right-to-left script shape)
+
+
+ `*BkSp*`
+ or
+ Backspace key (shape determined by writing system direction)
+
+
+ `*LTRBkSp*`
+
+ Backspace key (left-to-right script shape)
+
+
+ `*RTLBkSp*`
+
+ Backspace key (right-to-left script shape)
+
+
+ `*Menu*`
+
+ Globe key; display the language menu. Use on the `K_LOPT` key.
+
+
+ `*Hide*`
+
+ Hide the on screen keyboard. Use on the `K_ROPT` key.
+
+
+ `*ABC*`
+
+ Select alphabetic layer (Uppercase)
+
+
+ `*abc*`
+
+ Select alphabetic layer (Lowercase)
+
+
+ `*123*`
+
+ Select the numeric layer
+
+
+ `*Symbol*`
+
+ Select the symbol layer
+
+
+ `*Currency*`
+
+ Select the currency symbol layer
+
+
+ `*ZWNJ*`
+ (iOS) or (Android)
+ Zero Width Non Joiner (shape determined by current platform)
+
+
+ `*ZWNJiOS*`
+
+ Zero Width Non Joiner (iOS style shape)
+
+
+ `*ZWNJAndroid*`
+
+ Zero Width Non Joiner (Android style shape)
+
+
+ `*ZWNJGeneric*`
+
+ Zero Width Non Joiner (not platform-specific)
+
+
+ `*Sp*`
+
+ Regular space
+
+
+ `*NBSp*`
+
+ No-Break Space
+
+
+ `*NarNBSp*`
+
+ Narrow No-Break Space
+
+
+ `*EnQ*`
+
+ En Quad
+
+
+ `*EmQ*`
+
+ Em Quad
+
+
+ `*EnSp*`
+
+ En Space
+
+
+ `*EmSp*`
+
+ Em Space
+
+
+ `*PunctSp*`
+
+ Punctuation Space
+
+
+ `*ThSp*`
+
+ Thin Space
+
+
+ `*HSp*`
+
+ Hair Space
+
+
+ `*ZWSp*`
+
+ Zero Width Space
+
+
+ `*ZWJ*`
+
+ Zero Width Joiner
+
+
+ `*WJ*`
+
+ Word Joiner
+
+
+ `*CGJ*`
+
+ Combining Grapheme Joiner
+
+
+ `*LTRM*`
+
+ Left-to-right Mark
+
+
+ `*RTLM*`
+
+ Right-to-left Mark
+
+
+ `*SH*`
+
+ Soft Hyphen
+
+
+ `*HTab*`
+
+ Horizontal Tabulation
+
+
+
+
+
+The following additional symbols are also available, but intended for working
+with legacy desktop layouts, and not recommended for general use:
+
+
+
+
+ Text String
+ Key Cap
+ Key Purpose
+
+
+
+
+ `*Tab*`
+
+ Move to next input element in tab order
+
+
+ `*TabLeft*`
+
+ Move to previous input element in tab order
+
+
+ `*Caps*`
+
+ Select caps layer (legacy)
+
+
+ `*AltGr*`
+
+ Select AltGr (Right-Alt) layer (desktop layout compatibility)
+
+
+ `*Alt*`
+
+ Select Alt layer (desktop layout compatibility)
+
+
+ `*Ctrl*`
+
+ Select Ctrl layer (desktop layout compatibility)
+
+
+ `*LAlt*`
+
+ Select Left-Alt layer (desktop layout compatibility)
+
+
+ `*RAlt*`
+
+ Select Right-Alt layer (desktop layout compatibility)
+
+
+ `*LCtrl*`
+
+ Select Left-Ctrl layer (desktop layout compatibility)
+
+
+ `*RCtrl*`
+
+ Select Right-Ctrl layer (desktop layout compatibility)
+
+
+ `*LAltCtrl*`
+
+ Select Left-Alt-Ctrl layer (desktop layout compatibility)
+
+
+ `*RAltCtrl*`
+
+ Select Right-Alt-Ctrl layer (desktop layout compatibility)
+
+
+ `*LAltCtrlShift*`
+
+ Select Left-Alt-Ctrl-Shift layer (desktop layout compatibility)
+
+
+ `*RAltCtrlShift*`
+
+ Select Right-Alt-Ctrl-Shift layer (desktop layout compatibility)
+
+
+ `*AltShift*`
+
+ Select Alt-Shift layer (desktop layout compatibility)
+
+
+ `*CtrlShift*`
+
+ Select Ctrl-Shift layer (desktop layout compatibility)
+
+
+ `*AltCtrlShift*`
+
+ Select Alt-Ctrl-Shift layer (desktop layout compatibility)
+
+
+ `*LAltShift*`
+
+ Select Left-Alt-Shift layer (desktop layout compatibility)
+
+
+ `*RAltShift*`
+
+ Select Right-Alt-Shift layer (desktop layout compatibility)
+
+
+ `*LCtrlShift*`
+
+ Select Left-Ctrl-Shift layer (desktop layout compatibility)
+
+
+ `*RCtrlShift*`
+
+ Select Right-Ctrl-Shift layer (desktop layout compatibility)
+
+
+
+
+### Key type
+
+The general appearance of each key is determined by the key type, which is
+selected (in Keyman Developer) from a drop-down list. While generally behavior
+is not impacted by the key type, Spacer keys cannot be selected.
+
+
+
+
+ Key Type
+ Value
+ Meaning
+
+
+
+
+ Default
+ `0`
+ Any normal key that emits a character
+
+
+ Special
+ `1`
+ The frame keys such as Shift, Enter, BkSp.
+
+
+ Special (active)
+ `2`
+ A frame key which is currently active, such as the Shift key on the shift layer.
+
+
+ Deadkey
+ `8`
+ Does not impact behavior, but colors the key differently to indicate it has a special function, such as a
+ desktop-style deadkey.
+
+
+ Blank
+ `9`
+ A blank key, which may be used to maintain a layout shape. Usually colored differently. Does not impact
+ behavior.
+
+
+ Spacer
+ `10`
+ Does not render the key, but leaves a same-sized gap in its place. The key cannot be selected.
+
+
+
+
+The colour, shading and borders of each key type is actually set by a style
+sheet which can be customized by the page developer.
+
+### font-family
+
+If a different font is required for a particular key text, the `font-family`
+name can be specified. The font used to display icons for the special keys (as
+mentioned above) does not need to be specified, as it will be automatically
+applied to a key that uses any of the special key text labels.
+
+### font-size
+
+If a particular key cap text requires a different font size from the default for
+the layout, it should be specified in em units. This can be helpful if a the key
+text is either an unusually large character or, alternatively, a word or string
+of several characters that would not normally fit on the key.
+
+### width
+
+The layout is scaled to fit the widest row of keys in the device width, assuming
+a default key width of 100 units. Keys that are to be wider or narrower than the
+default width should have width specified as a percentage of the default width.
+For any key row that is narrower than the widest row, the width of the last key
+in the row will be automatically increased to align the right hand side of the
+key with the key with the right edge of the keyboard. However, where this is not
+wanted, a "spacer" key can be inserted to leave a visible space instead. As
+shown in the above layouts, where the spacer key appears on the designer screen
+as a narrow key, but will not be visible in actual use.
+
+### pad
+
+Padding to the left of each key can be adjusted, and specified as a percentage
+of the default key width. If not specified, a standard padding of 5% of the key
+width is used between adjacent keys.
+
+### layer
+
+To simplify correspondence with desktop keyboards and avoid the need for using a
+separate keyboard mapping program, touch layout keys can specify a desktop
+keyboard layer that the keystroke should be interpreted as coming from. Layer
+names of `shift`, `ctrl`, `alt`, `ctrlshift`, `altshift`, `ctrlalt` and
+`ctrlaltshift` can be used to simulate use of the appropriate modifier keys when
+processing rules.
+
+### nextlayer
+
+The virtual keys `K_SHIFT`, `K_CONTROL`, `K_MENU`, etc. are normally used to
+switch to another key layer, which is implied by the key code. The left and
+right variants of those key codes, and also additional layer-switching keys
+mentioned above (`K_NUMERALS`, `K_SYMBOLS`, `K_CURRENCIES`, `K_ALTGR`) can also
+be used to automatically switch to the appropriate key layer instead of
+outputting a character. However, it is sometimes useful for a key to output a
+character first, then switch to a new layer, for example, switching back to the
+default keyboard layer after a punctuation key on a secondary layer had been
+used. Specifying the `nextlayer` for a key allows a different key layer to be
+selected automatically following the output of the key. Of course, that can be
+manually overridden by switching to a different layer if preferred.
+
+Another way the `nextlayer` property can be used is for a non-standard layer
+switching key. So, for example, for the GFF Amharic keyboard phone layout,
+switching back to the base layer uses a `T_ALPHA` key code, in which `nextlayer`
+is set as default. In this case, it is also necessary to add a rule to the
+keyboard program:
+
+```keyman
++ [T_ALPHA] > nul
+```
+
+to ensure that the key's scan code is ignored by the keyboard mapping.
+
+When a key in a touch layout definition includes a **Next Layer** control, this
+takes precedence over setting layer via the
+[`layer`](/developer/language/reference/layer) store (as the **Next Layer**
+control is applied once the rule has finished processing).
+
+### subkey
+
+Arrays of longpress 'subkeys' or pop-up keys can be defined for any key, and
+will appear momentarily after the key is touched if not immediately released.
+This provides a major advantage over physical desktop keyboards in that many
+more keys can be made available from a single layer, without cluttering up the
+basic appearance of the layout. For the GFF Amharic keyboard, we have already
+noted how such subkey arrays are used to manage the extra keys that, on the
+desktop keyboard, would appear in the shift layer. But they are also used to
+provide another way to enter the two different types of each syllable-initial
+vowels (glottal or pharyngeal), as a visual alternative to pressing the key
+twice.
+
+The same properties that are defined for standard keys can also be specified for
+each subkey except that the width of each key in a subkey array will always be
+the same as the width of the key that causes the subkeys to be shown, and key
+spacing always uses the default padding value.
+
+The GFF Amharic keyboard, like many others, is mnemonic, so it is useful to also
+display the standard key cap letter that would appear on the key of a desktop
+keyboard. This is enabled globally in the On-Screen layout editor and applies to
+both the On-Screen keyboard and touch layouts.
+
+## Representing (and editing) the visual layout with JSON code
+
+In case you are wondering, 'Why do I need to know that?', the reason is that,
+just as with keyboard mapping code, it is sometimes easier to edit a text
+specification than to use the GUI layout design tool. Keyman Developer switches
+seamlessly between the visual layout tool and the code editor, unless, of
+course, careless editing of the code results in invalid JSON syntax!
+
+The GFF Amharic phone layout code starts as:
+
+```javascript
+ {
+ "phone": {
+ "font": "Tahoma",
+ "layer": [{
+ "id": "default",
+ "row": [{
+ "id": 1,
+ "key": [{
+ "id": "K_Q",
+ "text": "ቅ",
+ "pad": "0"
+ }, {
+ "id": "K_W",
+ "text": "ው"
+ },
+
+ . . .
+```
+
+As long as standard JSON syntax is remembered - nested braces {…}, quoted
+strings "…" for both element names and values, element or object arrays in
+square brackets […], and no trailing comma after the last element in an array -
+it is quite easy to understand a layout, which will usually comprise a list of
+two separate JSON objects for tablet and phone.
+
+Now you're ready to create a great touch layout for your own Keyman keyboard!
+
+Other articles on developing touch layouts:
+
+- [Creating a touch keyboard layout for Amharic - part 1](creating-a-touch-keyboard-layout-for-amharic)
+- [How to test your keyboard layout — touch and desktop](../test/keyboard-touch-and-desktop)
+
+You can distribute your keyboard to other users by following the instructions in
+this article:
+
+- [Distribute keyboards to Keyman applications](../distribute/packages)
diff --git a/developer/docs/help/guides/develop/creating-a-touch-keyboard-layout-for-amharic.md b/developer/docs/help/guides/develop/creating-a-touch-keyboard-layout-for-amharic.md
new file mode 100644
index 00000000000..3dc96dfc950
--- /dev/null
+++ b/developer/docs/help/guides/develop/creating-a-touch-keyboard-layout-for-amharic.md
@@ -0,0 +1,124 @@
+---
+title: Creating a Touch Keyboard Layout Part 1
+---
+
+This article describes the approach taken and some of the issues encountered when adapting the existing GFF‑AMH‑7 Amharic keyboard for easier use on phone and tablet touch screen devices.
+
+A widely used Keyman Desktop phonetic keyboard for Amharic (developed by Geez Frontier Foundation) was also compiled and made available for use with KeymanWeb. When used with web browsers on desktop computers, the behaviour of the keyboard was identical to use with Keyman Desktop, but use of the same Amharic keyboard on smart phones or other touch screen devices revealed a number of weaknesses in the layout.
+
+### Background
+
+The desktop layout has a default layer and a shift layer which are fairly similar:
+
+![](../../images/touch_amharic_keyboard.png)
+
+
+Several keycaps have more than one Geez script letter, indicating that pressing the key twice changes the character output. Geez script is better understood as an Abugida, rather than an alphabet, since each consonant-vowel syllable is a modification of the original letter rather than having separate letters for consonant and vowel. So when entering Amharic, the form of a consonant changes from the default syllable form to the appropriate syllable character for the consonant and vowel pressed. However, as each vowel can also occur in stand-alone syllables as either pharyngeal or glottal forms, the two separate syllables for each vowel are shown on the "a", "e", "i", "o" and "u" keys.
+
+But what about the extra characters on the "s" and "h" keys? Amharic (like most Semitic languages) is rich in fricative sounds, and Geez has four separate, distinct phonemes that are roughly similar to the English
+"s", and three similar to "h". The desktop layout handles this by using
+repeated keystrokes in combination with shift to select the alternate
+consonants (before typing the appropriate vowel). That works for a
+desktop keyboard, but not for a touch screen device, for which it is
+often impractical to hold a shift key while touching another key.
+
+Small format touch-screen devices such as phones have a further
+difficulty in that if they display the full number of key rows and keys
+in each row that appear in the desktop layout, the keys are too small
+for convenient use.
+
+On the other hand, touch screen devices offer one very significant
+advantage over layouts based on a physical keyboard - it is quite easy
+to configure dynamic touch screen layouts to display contextually
+appropriate character alternates in separate key windows as each key is
+touched.
+
+### Design goals
+
+With the above background, the following design goals were set for
+developing a touch screen layout for Amharic:
+- Maintain the same basic phonetic layout functionality that users are familiar with.
+- Instead of having "default" and "shift" layers, design the layout so that all alphabetic text and frequently used punctuation can be entered from a single layer, moving less commonly used punctuation and symbols to the second "symbol" layer.
+- Use a dynamic window rather than the shift key to select the initial "s" or "h" consonant for a syllable.
+- Use a separate key (adjacent to the space bar) for the Ethiopic word-space character, instead of requiring shift + space to be pressed.
+- For tablet devices, increase key size slightly by removing most non-letter keys from each row, moving them either to dynamic ("pop-up") key windows, or to the symbol layer.
+- For phones and other small-format devices, further increase the key size by not displaying the digits in the default (alphabetic) layer, and moving many of the less commonly used characters to dynamic windows, reducing the number of key rows by one for each layer.
+
+### Implementation
+
+Achieving the design goals required making a number of decisions for
+each format (tablet and phone), in particular:
+
+- Which keys can be removed from the default layer?
+- Which keys can be removed from the symbol layer?
+- Which "pop-up" keys should be grouped with each base character?
+
+Keyman Developer 17's Touch Layout editor greatly simplifies the process
+of developing the JSON layout file that specifies the touch layout for
+each device, making it easy to move keys around, create pop-up key
+arrays, and adjust key widths, labels and other parameters. While the
+graphical interface does not allow moving entire key rows between layers
+or device types, that is easily managed using the code window.
+
+To keep a keyboard compatible with the desktop version, it is important
+to use the same key identifiers and "shift states". So, no matter
+whether a particular character occurs in a visible row or in a pop-up
+array, it should usually use the same code as on the original desktop
+layout keyboard.
+
+Where a key did not occur in the original layout and does not correspond
+to a desktop layout key, identifiers will usually be something like
+T\_<name> where <name> will typically be a Unicode character
+name, or an obvious contraction of it. Note that this is just a rule of
+thumb and key identifiers can be anything that makes sense. Such keys
+are distinct from the K\_<name> identifiers that are associated
+with physical keys, and should normally have a mapping rule added
+explicitly to the mapping program. If no rule is added, output will
+default to the keycap text, but this is not recommended.
+
+All key "rotations" - pressing the same key repeatedly to select from
+alternate output characters - in the original desktop keyboard program
+are still supported, but have been supplemented by pop-up key arrays,
+allowing a user to visually select the wanted character instead of
+stepping through alternates. Similarly, Ethiopian numerals can be
+selected from a pop-up key array attached to each numeral key, as well
+as being output by pressing the single quote key before a digit key.
+
+The final tablet default keyboard layer, as displayed in the Touch
+Layout editor in the previous release of Keyman Developer 10, now appears
+as:
+
+![](../../images/touch_amharic_keyboard_2.png)
+
+Here the editor is shown with the "s" key selected, displaying the
+pop-up key array with the four different Geez characters that can be
+produced, each of which will combine as necessary with any subsequently
+typed vowel.
+
+For the small format (e.g. iPhone) layout, the two keyboard layers are:
+
+![](../../images/touch_amharic_keyboard_3.png)
+
+For these layouts, the row length has been reduced to the minimum number
+of ten keys, moving backspace to another row. The numerals only appear
+on the second layer, as is usual with phone layouts. All less-frequently
+used keys are now available only in pop-up key windows.
+
+### Summary
+
+With these principles guiding all the changes made, the Amharic keyboard
+now adapts to various device form factors, making use of device-specific
+usability factors, while retaining the same conceptual input model as
+the original keyboard. This means that users can use the keyboard layout
+on different devices with a minimum of difficulty. These methods should
+be applicable to other keyboard layouts.
+
+Other articles on developing touch layouts:
+
+- [Creating a touch keyboard layout for Amharic - the nitty gritty](creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty)
+- [How to test your keyboard layout — touch and desktop](../test/keyboard-touch-and-desktop)
+
+You can distribute your keyboard to other users by following the
+instructions in this article:
+
+- [Distribute keyboards to Keyman applications](../distribute/packages)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/imx/imxdll.md b/developer/docs/help/guides/develop/imx/imxdll.md
new file mode 100644
index 00000000000..8d4fe029bcd
--- /dev/null
+++ b/developer/docs/help/guides/develop/imx/imxdll.md
@@ -0,0 +1,115 @@
+---
+title: DLL Exports
+---
+
+The DLL is called from Keyman with `LoadLibrary()`. All functions are
+then found with `GetProcAddress()`. You must ensure that the function
+exports do not have ordinals encoded in the names. The best way to
+accomplish this in C/C++ is to use a .def file.
+
+## DLL group function exports
+
+The function declaration for the DLL group function is:
+
+```c
+BOOL WINAPI KeyEvent(HWND hwndFocus, WORD KeyStroke, WCHAR KeyChar, DWORD ShiftFlags);
+```
+
+Note that `KeyEvent()` is a placeholder for any name that you wish to
+use. You can have multiple exports for Keyman use in a single DLL.
+
+| | |
+|------------|-------|
+| hwndFocus | The currently focused window. You will probably never have a need to use this. |
+| KeyStroke | The virtual key code for the current key. |
+| KeyChar | The character code for the current key (based on US English layout). This will be `0` if _KeyStroke_ does not generate a character (e.g. function keys). |
+| ShiftFlags |The shift state for the current key. The following shift states are possible: (see the table below) |
+
+| Flag | Value | Description |
+|--------------|----------|----------------------------------------|
+| LCTRLFLAG | `0x0001` | Left Control Flag |
+| RCTRLFLAG | `0x0002` | Right Control Flag |
+| LALTFLAG | `0x0005` | Left Alt Flag |
+| RALTFLAG | `0x0008` | Right Alt Flag |
+| K_SHIFTFLAG | `0x0010` | Shift flag |
+| K_CTRLFLAG | `0x0020` | Ctrl flag (unused here; see l/r flags) |
+| K_ALTFLAG | `0x0040` | Alt flag (unused here; see l/r flags) |
+| CAPITALFLAG | `0x0100` | Caps lock on |
+| NUMLOCKFLAG | `0x0400` | Num lock on |
+| SCROLLFLAG | `0x1000` | Scroll lock on |
+| ISVIRTUALKEY | `0x4000` | It is a Virtual Key Sequence |
+
+## Optional DLL Exports
+
+Keyman recognises a number of other exports, if they are defined in the
+DLL. None of these are required. These functions will be called when a
+keyboard that references the DLL is manipulated. They will not be called
+for keyboards that do not reference the DLL.
+
+The following exports are available:
+
+### KeymanIMInit
+
+```c
+BOOL WINAPI KeymanIMInit(PSTR keyboardname);
+```
+
+`KeymanIMInit()` is called once when the keyboard identified by
+`keyboardname` is loaded for a given process. It is called for each
+process in which the keyboard is loaded.
+
+### KeymanIMDestroy
+
+```c
+BOOL WINAPI KeymanIMDestroy(PSTR keyboardname);
+```
+
+This is called once when the keyboard identified by `keyboardname` is
+unloaded in a given process. It is called when the process exits
+normally, or when Keyman refreshes its keyboard list after keyboards are
+added or removed. If the keyboard is subsequently reloaded,
+`KeymanIMInit()` will be called again.
+
+### KeymanIMActivate
+
+```c
+BOOL WINAPI KeymanIMActivate(PSTR keyboardname);
+```
+
+This function is called whenever the user or a program activates the
+keyboard. It is never called before `KeymanIMInit()` (unless
+`KeymanIMInit()` is not exported). This is an appropriate place to
+switch on permanently-visible IMC windows. `KeymanIMActivate()` can also
+be called when switching processes and the target process has a related
+keyboard already active.
+
+### KeymanIMDeactivate
+
+```c
+BOOL WINAPI KeymanIMDeactivate(PSTR keyboardname);
+```
+
+This function is called when the user or a program switches off a
+related keyboard. It is always called before `KeymanIMActivate()` for
+the next keyboard. It will also be called when the user activates
+another process, to give the DLL a chance to hide top-most IMC windows.
+
+### KeymanIMConfigure
+
+```c
+BOOL WINAPI KeymanIMConfigure(PSTR keyboardname, HWND hwndParent);
+```
+
+`KeymanIMConfigure()` is called when the user clicks the Configure
+button in Keyman Configuration to configure the DLL-specific
+functionality for the keyboard. The appropriate behaviour is to display
+a dialog box, and save the settings in the registry.
+
+This function is separate from all the other functions. It can be called
+when there are no keyboards loaded, or even if Keyman itself is not
+loaded. You should not attempt to call Keyman32.dll from this function.
+
+## See also
+
+- [DLL Interface for Keyman - Introduction](index)
+- [The imlib.cpp library module](imxlib)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/imx/imxlib.md b/developer/docs/help/guides/develop/imx/imxlib.md
new file mode 100644
index 00000000000..178ae0f3eff
--- /dev/null
+++ b/developer/docs/help/guides/develop/imx/imxlib.md
@@ -0,0 +1,208 @@
+---
+title: The imlib.cpp library module
+---
+
+imlib.cpp, included in the development kit, contains a set of useful
+functions for interfacing to Keyman.
+
+## PrepIM
+
+```c
+BOOL PrepIM(void);
+```
+
+`PrepIM()` initialises the Keyman32 imports. You should not call any of
+the Keyman imports without calling `PrepIM()` first. If `PrepIM()`
+fails, you should exit without doing any processing.
+
+## IMDefWindowProc
+
+```c
+BOOL IMDefWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LRESULT *lResult);
+```
+
+`IMDefWindowProc()` should be called from an IMC window procedure (see
+section titled Input Method Composition windows). If it returns `TRUE`
+you should return the value stored in `lResult` without any further
+processing. `IMDefWindowProc()` mostly manages window activation and
+movement.
+
+## Keyman Imports
+
+The DLL can call Keyman functions to interact with Keyman and the target
+application. It should not attempt to directly control the application
+as Keyman will be doing this. You should never call any of the functions
+here from the `KeymanIMConfigure()` callback.
+
+You can use `PrepIM()`, declared in imlib.cpp to get access to the the
+Keyman functions. When using imlib.cpp, the functions are declared as
+pointers, so you need to dereference them to call them in C (e.g. for
+KMGetContext, call `(*KMGetcontext)(buf,len);`)
+
+## KMGetContext
+
+```c
+BOOL WINAPI KMGetContext(PWSTR buf, DWORD len);
+```
+
+`KMGetContext()` returns the last `len-1` UTF-16 codepoints of the
+context stack. If there are not enough characters in the context stack,
+it will return as many as it can. On success, the `buf` variable will be
+null terminated.
+
+The context stack can contain a special code for deadkeys. See
+`KMQueueAction()` for a way to output a deadkey. The code sequence for a
+deadkey is (3 words):
+
+```
+UC_SENTINEL, CODE_DEADKEY, deadkeyID
+```
+
+`UC_SENTINEL` is `0xFFFF`; `CODE_DEADKEY` is `0x0008`; `deadkeyID` can
+be any value from `0x0001` to `0xFFFE`.
+
+> ### Note
+Changes in 8.0.333.0: a potential buffer overflow has been corrected.
+The size of the buffer pointed to by `buf` should be `WCHAR[len+1]`
+to allow for terminating null. Keyman now counts supplementary plane
+characters as 2 UTF-16 codepoints rather than as a single codepoint.
+Keyman no longer returns partial deadkey code sequences.
+
+## KMSetOutput
+
+```c
+BOOL WINAPI KMSetOutput(PWSTR buf, DWORD backlen);
+```
+
+`KMSetOutput()` is a wrapper for `KMQueueAction()`. It simplifies the
+process of deleting contextual characters and outputting a new string.
+The results will not be output to the screen until the current function
+returns. If called within the context of an IMC window, the results will
+not be output to the screen until the window posts the
+`wm_keymanim_close` message.
+
+`buf` is a pointer to a null-terminated string of characters to output.
+`backlen` is the number of characters to backspace from the current
+context before displaying `buf`.
+
+This function modifies the context returned from `KMGetContext()`, even
+if the output is not yet on the screen.
+
+Internally, this function does the following:
+
+```c
+while(backlen-- > 0) KMQueueAction(QIT_BACK, 0);
+while(*buf) KMQueueAction(QIT_CHAR, *buf++);
+```
+
+## KMQueueAction
+
+```c
+BOOL WINAPI KMQueueAction(int itemType, DWORD dwData);
+```
+
+`KMQueueAction()` lets you send any Keyman action to a target
+application. This can be virtual keys, characters, shift keys up and
+down, deadkeys, beeps, or backspaces (a special case of virtual keys).
+
+| | |
+|------------------|----------------|
+| itemType code | Description |
+| `QIT_VKEYDOWN` | Simulate any key press on the keyboard; `dwData` is the virtual key code |
+| `QIT_VKEYUP` | Simulate any key release on the keyboard; `dwData` is the virtual key code |
+| `QIT_VSHIFTDOWN` | Simulate pressing a set of shift keys. `dwData` can be a combination of the following flags: `LCTRLFLAG, RCTRLFLAG, LALTFLAG, RALTFLAG, K_SHIFTFLAG, K_CTRLFLAG, K_ALTFLAG` |
+| `QIT_VSHIFTUP` | Release the shift state, `dwData` is the same as the previous flags. |
+| `QIT_CHAR` | `dwdata` is any `WCHAR`. |
+| `QIT_DEADKEY` | `dwData` is any value from `0x0001` to `0xFFFE`. This can be matched in the context with `KMGetContext()`. |
+| `QIT_BELL` | `dwData` should be zero (`0`). |
+| `QIT_BACK` | `dwData` should be zero (`0`). |
+
+## KMHideIM
+
+```c
+BOOL WINAPI KMHideIM(HWND hwndIM);
+```
+
+`KMHideIM()` hides the IMC window referred to by `hwndIM` and ensures
+that Keyman processes input from the keyboard through the correct
+method. You should call this rather than hiding the window manually with
+`ShowWindow(hwnd, SW_HIDE);` or post the message `wm_keymanim_close` to
+hide the window.
+
+## KMDisplayIM
+
+```c
+BOOL WINAPI KMDisplayIM(HWND hwndIM, BOOL FCaptureAll);
+```
+
+`KMDisplayIM()` displays the IMC window referred to by `hwndIM`. It does
+not do any movement of the window. If the `FCaptureAll` flag is set, all
+keyboard input (character-generating keys only) will be redirected to
+the IMC window until the message `wm_keymanim_close` is posted,
+`KMHideIM()` is called, or `KMDisplayIM()` with `FCaptureAll` set to
+`FALSE`.
+
+## KMGetKeyboardPath
+
+```c
+BOOL WINAPI KMGetKeyboardPath(PSTR keyboardname, PWSTR dir, DWORD length);
+```
+
+This function returns the full path to the keyboard referred to by
+`keyboardname`. The buffer `dir` should be 260 characters long.
+
+## KMGetActiveKeyboard
+
+```c
+BOOL WINAPI KMGetActiveKeyboard(PSTR keyboardname, DWORD length);
+```
+
+This function can be called while processing to determine which is the
+active keyboard. Alternatively, use the callbacks `KeymanIMActivate()`
+and `KeymanIMDeactivate()`.
+
+## KMSendDebugString
+
+```c
+BOOL WINAPI KMSendDebugString(PSTR str);
+```
+
+This function outputs the string `str` to the Keyman debug window or
+debug log file (usually %USERPROFILE%\Desktop\keymanlog\system\*.log).
+
+## The Input Method Composition window
+
+The IMC window can be shown or hidden at any time that the associated
+keyboard is active. This means that you can have an IMC window
+permanently open or open at appropriate times.
+
+The keyboard IMSample included with Keyman is a good example of
+manipulating the IMC display.
+
+The window should be created invisible, most probaly as a popup window.
+The window can use `KMGetContext()`, `KMSetOutput()` at any time, but
+output will not be put to the screen until it has posted (not sent)
+`wm_keymanim_close` to itself.
+
+```c
+PostMessage(hwnd, wm_keymanim_close, (WPARAM) FSuccess, (LPARAM) FActuallyClose);
+```
+
+Keyman will manage the window display, focus, and message loop. The
+window procedure should set the position and size appropriately.
+
+Keyman will recognise this window and any child windows to be part of
+the IM and will not attempt to process any input that goes through the
+window.
+
+The IMC window must not take focus at any time.
+
+## Limitations
+
+- Clicks outside the window will cancel the IM and lose context.
+- Switching applications will cancel the IM and lose context.
+
+## See also
+
+- [DLL Interface for Keyman - Introduction](index)
+- [DLL Exports](imxdll)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/imx/index.md b/developer/docs/help/guides/develop/imx/index.md
new file mode 100644
index 00000000000..6bc606b8f91
--- /dev/null
+++ b/developer/docs/help/guides/develop/imx/index.md
@@ -0,0 +1,101 @@
+---
+title: Input Method Extensions
+---
+
+Keyman's multiple group processing is powerful, but sometimes you need
+to be able to do something a bit more complex, such as a dictionary
+lookup. Keyman's DLL interface let you do this. You can call a function
+in a DLL in the same way as you call another group. The function can
+read the context, deadkeys and the current keystroke, and output
+characters, deadkeys, virtual keys, beeps and other items.
+
+The DLL interface also allows you to create a popup Input Method
+Composition (IMC) window. This window allows the user to select visually
+the characters they are wishing to input. The window can be set to be
+visible when the keyboard is active, or after an appropriate key
+sequence. When the window is visible, it can be set to capture all
+keyboard input, or be passive.
+
+## File locations
+
+The DLL should be placed in any of the following locations:
+
+- The same directory as the .kmx file (e.g. use a package to install it)
+- The Keyman program directory (same place as keyman32.dll)
+- Anywhere on the path (such as the Windows directory)
+
+The best option is the first, as you can then include the DLL in a
+Keyman package for easy installation and uninstallation.
+
+Full x64 support for IMX DLLs was introduced in Keyman Desktop
+8.0.333.0. An x64 version of the DLL may be included simply by giving it
+the file extension .x64.dll, with the same base name as the x86 DLL.
+Keyman will call the appropriate DLL.
+
+## General usage information
+
+DLL functions used in place of groups are called DLL group functions.
+
+All strings, apart from keyboard names, are passed as WCHAR, regardless
+of whether the active window is a Unicode window or not. In Keyman 8 and
+earlier, ANSI characters are represented as 16-bit WCHAR, with high bits
+zeroed out. In Keyman 9, ANSI keyboards are translated to Unicode at
+install time, through the CP-1252 code page.
+
+The DLL will be loaded for each process in which the keyboard is
+activated. Remember that the DLL will not share memory between these
+processes by default, so if you have large memory requirements, you
+should use memory mapped files or possibly SHARDATA segments to minimize
+the memory consumption.
+
+DLL group functions are called in a fairly time-critical environment. It
+is important that you minimise the processing time in these functions.
+It is essential that you avoid any window focus or activation – message
+boxes are definitely out of the question. For debugging purposes, there
+is a Keyman32.dll function exported for writing to the logfile (see the
+section titled Keyman32 imports).
+
+DLLs can handle multiple keyboards at once. The keyboards are identified
+by a name which is the filename of the keyboard, minus path and
+extension. For example, given c:\keyman\imsample\imsample.kmx, the
+keyboard name is imsample. These are the same names that Keyman uses
+internally, for example in the registry and directory names.
+
+## Registry settings
+
+Parameters for the DLL can be stored in two locations in the registry.
+They should always be stored under HKEY_CURRENT_USER, as the user will
+not have permission to change machine-wide settings, and the settings
+should not affect other users. The following locations are recommended:
+
+> HKEY_CURRENT_USER\Software\Tavultesoft\Keyman Engine\9.0\IMX\\[DLLName]
+
+> HKEY_CURRENT_USER\Software\Tavultesoft\Keyman Engine\9.0\Installed Keyboards\\[kbdname]
+
+The first key should be used for settings that pertain to any keyboard
+associated with the DLL. The second key should be used for any
+keyboard-specific settings. Values stored under the second key should be
+prefixed with the name of the dll, so that they will not conflict with
+Keyman or other dll values.
+
+## The .kmn interface
+
+Inside a .kmn file, you define the DLL group function interface as follows:
+
+```keyman
+store(DLLFunction) "myfile.dll:KeyEvent"
+```
+
+You can use this anywhere where you would place the use statement
+(except in the begin statement), with the call statement. For example,
+
+```keyman
++ 'a' > call(DLLFunction)
+```
+
+A single .kmn file can reference multiple DLL group functions, in a single or multiple DLLs.
+
+## See also
+
+- [DLL Exports](imxdll)
+- [The imlib.cpp library module](imxlib)
diff --git a/developer/docs/help/guides/develop/imx/web.md b/developer/docs/help/guides/develop/imx/web.md
new file mode 100644
index 00000000000..fa98ba40321
--- /dev/null
+++ b/developer/docs/help/guides/develop/imx/web.md
@@ -0,0 +1,4 @@
+---
+title: IMX Interface for KeymanWeb
+---
+
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/imx/web/index.md b/developer/docs/help/guides/develop/imx/web/index.md
new file mode 100644
index 00000000000..9453c5a01de
--- /dev/null
+++ b/developer/docs/help/guides/develop/imx/web/index.md
@@ -0,0 +1,5 @@
+---
+title: Guidelines for building IMX for KeymanWeb
+---
+
+* TODO: Write to fill requirements for .call_js, see call() statement for details.
diff --git a/developer/docs/help/guides/develop/index.md b/developer/docs/help/guides/develop/index.md
new file mode 100644
index 00000000000..8d8490c42c1
--- /dev/null
+++ b/developer/docs/help/guides/develop/index.md
@@ -0,0 +1,19 @@
+---
+title: Developing Keyboards
+---
+
+[Keyman keyboard tutorial](tutorial)
+
+[An advanced keyboard development example](advanced-keyboard-development-example)
+
+[Creating a simple touch keyboard layout](touch-keyboard-tutorial)
+
+[Creating a touch keyboard layout for Amharic - part 1](creating-a-touch-keyboard-layout-for-amharic)
+
+[Creating a touch keyboard layout for Amharic - part 2](creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty)
+
+[Input Method Extensions (IMX)](imx)
+
+------------------------------------------------------------------------
+
+[Keyman Developer 16 Tutorial](https://lingtran.net/Keyman-Developer-16-Tutorial) (external on Lingtran.net)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/touch-keyboard-tutorial/index.md b/developer/docs/help/guides/develop/touch-keyboard-tutorial/index.md
new file mode 100644
index 00000000000..bc7c8e792f2
--- /dev/null
+++ b/developer/docs/help/guides/develop/touch-keyboard-tutorial/index.md
@@ -0,0 +1,54 @@
+---
+title: Creating a Simple Touch keyboard
+---
+
+## Introduction and design
+
+If you have experience in designing Keyman keyboards for computers, the
+interface for designing mobile keyboards looks very similar, but it
+hides the reality of additional steps necessary for mobile keyboards.
+This tutorial attempts to walk you through the process.
+
+The [Quick French tutorial](../tutorial/) shows how to create a simple
+desktop keyboard for typing accented characters used in French and other
+European languages. It is possible to create a touch screen format of
+that keyboard, but it is probably unnecessary. The default keyboards for
+both Android and iOS devices have a way to type these accented
+characters. Press and hold on a vowel, and a popup menu will give you a
+choice of accents to put on that letter.
+
+For this tutorial we will demonstrate something usable for the Fulfulde
+language cluster spoken across many Sahelian countries of Africa.
+Fulfulde is usually written using the Latin alphabet, but there are a
+few characters added to the usual list of alphabetical characters. So
+this is a useful example for any language wanting to add a few
+characters to the standard Latin keyboard. Here are the characters we
+are going to add, together with their Unicode values.
+
+| | | | | | | | | |
+|-----|----------|-----|----------|-----|-----|----------|-----|----------|
+| `ɓ` | `U+0253` | `Ɓ` | `U+0181` | | `ɗ` | `U+0257` | `Ɗ` | `U+018A` |
+| `ŋ` | `U+014B` | `Ŋ` | `U+014A` | | `ƴ` | `U+01B4` | `Ƴ` | `U+01B3` |
+
+Here we will likely want to proceed differently than with the desktop
+keyboard. The usual approach for a desktop keyboard is to create a
+deadkey. The keyboards I have used for Fulfulde (going back to decades
+ago) used the / key as the deadkey.
+/ b gave ɓ,
+/ d gave ɗ, and so on.
+
+It would be possible to use that desktop keyboard logic on touch
+screens. But your mobile device users will find these rules annoying.
+Instead of two keys they would probably have to press four, since the
+standard alphabetical keyboard on most touch devices does not have the
+slash key. So they would have to press the key to switch to the numeric
+keyboard, then press slash, then press the key to go back to the
+alphabetical keyboard, then the letter. What touch screen users would
+appreciate is something that uses the great feature of touch screens,
+the long press or press and hold on a letter, to see analogous letters.
+So we’ll set up a touch screen keyboard that lets you press and hold ‘d’
+to see the two hooked d characters, press and hold ‘b’ to see the hooked
+b characters, and so on.
+
+[Next: Making, testing and distributing the touch
+keyboard](making-touch-keyboard)
diff --git a/developer/docs/help/guides/develop/touch-keyboard-tutorial/making-touch-keyboard.md b/developer/docs/help/guides/develop/touch-keyboard-tutorial/making-touch-keyboard.md
new file mode 100644
index 00000000000..5039acb2624
--- /dev/null
+++ b/developer/docs/help/guides/develop/touch-keyboard-tutorial/making-touch-keyboard.md
@@ -0,0 +1,361 @@
+---
+title: Making a touchscreen keyboard
+---
+
+## Setting up your Keyman project
+
+This is a useful convention for keeping track of your files:
+
+1. Create a folder on your hard drive for all your Keyman developer
+ files
+2. In that folder, create a folder for this project, named after the
+ language. Use all lowercase letters for compatibility with other
+ platforms.
+3. Now create a project in Keyman Developer with the same name as the
+ new folder, and save it inside the new folder
+
+Now you will be able to use this project to organize and create any
+files for desktop, mobile, web or other. It also facilitates uploading
+the files to the Keyman website later on.
+
+## Add touch to an existing keyboard or create a new keyboard?
+
+You can add a mobile touchscreen keyboard to an existing desktop
+keyboard, or you can create a new touchscreen keyboard from scratch. You
+should be aware that there will be cross-over between mobile and desktop
+keyboards. (In Keyman terms, "desktop" includes laptop computers or any
+device with a physical keyboard). Your mobile device can have an
+external keyboard, which will use the desktop Keyman keyboard. So the
+Keyman strategy is to always bundle touch keyboards with desktop
+keyboards to make them work on multiple platforms.
+
+If creating from scratch, you click the New icon in the Keyman Developer
+toolbar (or select File > New from the menu), and specify the file
+name for your keyboard. We recommend a name using only lowercase
+letters, numbers and underscore. I’ll choose “mobile_fulfulde.kmn” .
+
+![choose new file
+type](../../../images/simpleTouchKeyboard_1.png)
+
+## Adding the required metadata
+
+After creating the file, Keyman Developer will ask you to name the
+keyboard. This name can include spaces and upper case letters if
+desired. I’ll name this one “Fulfulde for Mobile”.
+
+![keyboard
+metadata](../../../images/simpleTouchKeyboard_2.png)
+
+The first thing you need to do is to tell Keyman Developer this keyboard
+will include some kind of mobile device. You do this in the Targets
+list just below the keyboard name. I’ll select “mobile ” in the list,
+(towards the end). You see in the picture above that Keyman Developer
+checked the “windows” box by default, I can uncheck that or leave it
+checked if I am going to add a Windows keyboard to this set.
+
+![mobile target](../../../images/simpleTouchKeyboard_3.png )
+
+This will include all mobile devices, both Android and iOS, both phones
+and tablets. It is possible if needed to select Android only or iOS only
+if needed, and to develop a different keyboard layout for tablets than
+for phones, but for this example, with only a few characters needed to
+be added to the default keyboards, there is no need for that kind of
+complexity.
+
+(If you are adding a mobile layout to an existing keyboard file, you
+would open that file in Keyman Developer, go to the Targets box and
+check “mobile” or whatever mobile targets you wanted to specify, then
+follow along from here).
+
+## Adding the touch optimized feature
+
+The next step is to add the touch-optimised feature to our keyboard
+setup. If you scroll to the bottom of the details tab, there is the list
+of features to add. I’ll click the Add button and choose “touch
+optimised keyboard.”
+
+![touch-optimised](../../../images/simpleTouchKeyboard_4.png)
+
+Keyman developer asks me to choose one of three templates for the mobile
+layout.
+
+![three templates](../../../images/simpleTouchKeyboard_5.png)
+
+The “template-latin” layout includes many accented characters, like the
+default keyboards for Android and iOS do. The “template-basic” layout
+has no extra characters, and the template-traditional has a few extra
+characters (mostly punctuation symbols if you long-press period or full
+stop). For this example, I’ll choose “template-basic” because accented
+characters are not needed for Fulfulde. But if I knew that many eventual
+users might want to be able to type French or other European languages
+as well as Fulfulde, and wouldn’t like to have to switch the keyboard to
+do so, I could choose “template-latin”.
+
+## Defining one or several touch layouts
+
+I select the template and click OK. Keyman Developer now adds a new tab
+in the left column, the Touch Layout tab.
+
+![touch layout](../../../images/simpleTouchKeyboard_6.png)
+
+That is where we need to go now to add characters to our touch layout.
+But before doing that, there is one important thing to take note of in
+the touch layout. By default, Keyman Developer has created a layout for
+phones and another layout for tablets. They all start out the same, but
+as I add characters, I will have to make sure I add all the characters
+to both layouts, or I’ll discover that my keyboard does not work for all
+device sizes.
+
+In the image, you see in the Platform box that Keyman Developer is currently showing the
+tablet layout. This box is where I’d choose which layout I’d design in.
+
+But I’m going to make this keyboard simple, and just have one layout for
+both tablets and phones. So in the platform box, I am going to click
+the “minus” button, to delete the tablet layout. When there is only one
+mobile layout, Keyman Developer will compile that layout for all mobile
+devices. (I could also have chosen to delete the phone layout and done
+my layout design in the tablet layout).
+
+![platform list](../../../images/simpleTouchKeyboard_7.png)
+
+## Adding longpress characters
+
+Now I can start adding characters. I’ll click on the “y” key in the
+layout, then I’ll look at the “longpress keys” area at the lower
+left.
+
+![add longpress](../../../images/simpleTouchKeyboard_8.png)
+
+I click that, and Keyman Developer adds a new key below the keyboard
+layout.
+
+![longpress added](../../../images/simpleTouchKeyboard_9.png)
+
+I'm going to use this key for the y with hook. I click inside it to make it active, then I’ll go look for my y
+with hook character in the character map to the right. If I type “y
+hook” in the search box, Keyman Developer shows the character I’m
+looking for.
+
+![adding keycap](../../../images/simpleTouchKeyboard_10.png)
+
+If I double click on the lower case y with hook, that copies the character
+to the keycap box of my new key. (I could also click and drag the character from the character map to the key).
+
+![keycap added](../../../images/simpleTouchKeyboard_11.png)
+
+## New key info
+
+There are several important pieces of information that got copied over. If we
+look at the properties of the new key to the right, we see that this new key has
+the “text” ƴ (what appears on the keycap), the “Unicode value” U+01B4 and the
+“ID” U_01B4. All of these were copied from the character map.
+
+NOTE: If you copy one character from the character map to a new key, then
+realize you copied the wrong character, dragging a second character may not
+replace all the needed information into the key. If a key has a character in it
+already, it works better to hold Ctrl as you drag the new character
+into it.
+
+I could add the other three characters; hook b, hook d, and eng, as longpress
+characters as well. I could select the regular letter I want to longpress,
+click plus in the longpress keys area and copy the needed character. Here for
+example is the b with hook:
+
+![keycap added](../../../images/simpleTouchKeyboard_12.png)
+
+After I have added my four lowercase characters as longpress keys, the keyboard
+looks like this. Even when I don't have that letter selected, I can see which
+keys have longpress keys by the dot beside the letter.
+
+![keycap added](../../../images/simpleTouchKeyboard_12-2.png)
+
+[See the Keyman Language guide on virtual keys](../../../../language/guide/virtual-keys#toc-virtual-keys-and-touch-layouts)
+
+## Adding uppercase characters
+
+I could do two different things for the upper case letters. I could add them as
+longpress keys beside their lowercase equivalents, by pressing the plus key
+again to add a second longpress character to each letter.
+
+This would mean users would not have to press Shift to be able to type the upper
+case hook y. But since a user needs to press Shift for all the regular Latin
+upper case letters, it might make more sense to put the uppercase letters into
+the Shift layer.
+
+I can change to the shift layer by finding the layer box at lower left, and
+choosing `shift` instead of `default`.
+
+![changing to shift layer](../../../images/simpleTouchKeyboard_13.png)
+
+Now I see the upper case letters on the keyboard, and I can add longpress
+characters to these in the same way I did on the default layer. I click the
+letter, then click `+` to add a key, then copy the character info from the
+character map. Adding my upper case y hook to Y would look like this:
+
+![new key added to row](../../../images/simpleTouchKeyboard_14.png)
+
+(If I had clicked on the green triangle to the left of my first key, the
+new key would be added to the left of that key).
+
+For this new key, I go through the same steps. I click in the empty box
+on the new key, then double click the upper case y with hook in the
+character map to put that in the keycap. I also change the code of this
+new key to “U_01B3”
+
+![keycap and code added to new
+key](../../../images/simpleTouchKeyboard_15.png)
+
+## Another option for upper case characters
+
+I might also add the upper case character to a longpress character on
+the upper-case Y key, because some users will press the Shift key then
+look for the letter to type. To do this, I change the layer at the top
+of the tablet layout to "shift" from "default".
+
+![changing
+layer](../../../images/simpleTouchKeyboard_15b.png)
+
+Now I click the Y key, and add a longpress character, then add the upper
+case y with hook to the keycap and add the keycode as shown before.
+
+![key added to longpress
+Y](../../../images/simpleTouchKeyboard_16b.png)
+
+You can design your keyboard either way, with the upper case characters
+in the same longpress row as the lower case, or in a longpress row off
+of the shift layer. The rest of this tutorial shows the upper case
+characters in the same row as the lower case, but I don't mean to
+present this as the best way. If you do put upper case characters in the
+shift layer, you'll have to remember to change the layer back to
+"default" when you want to add a lower case letter, then back to "shift"
+when adding an upper case letter. You could put the upper case
+characters in both places, if you think that best.
+
+Now I’ll add the two d hook characters as a longpress popup for the d
+key. I click the d key in the layout, click “Add longpress popup”, add
+the lower case hook d to the keycap and change the code to “U_0257”;
+then click the plus sign to add a second key, add the upper case hook D
+to the keycap and change its code to “U_018A”. The end result should
+look like this:
+
+![longpress row on
+d](../../../images/simpleTouchKeyboard_16.png)
+
+I’ll do the similar step to add two characters as a longpress popup for
+“b”
+
+![longpress row on
+b](../../../images/simpleTouchKeyboard_17.png)
+
+And two characters as a longpress popup for “n”. (Tip, to search for ŋ
+in the character map, type “eng”).
+
+![longpress row on
+n](../../../images/simpleTouchKeyboard_18.png)
+
+When no keys are selected in the keyboard layout, I can still see which
+ones I have set up longpress popups for, because Keyman Developer shows
+a faint gray line at the top right corner of the key. So you should have
+a faint diagonal line on your y, d, b and n keys.
+
+![lines on longpress
+keys](../../../images/simpleTouchKeyboard_19.png)
+
+## Replacing the Unicode and Code automatically
+
+1. Hold the Ctrl key while dragging and
+ dropping a character from the Character Map to the touch layout
+ editor, and it will replace the Code as well as the character.
+2. Hold the Shift key while dropping, and it
+ will only add the character to the key.
+3. Finally, hold
+ Ctrl +Shift , this
+ will add the character to the key AND update the Code to match the
+ full set of characters represented on the key.
+
+## Compiling your keyboard
+
+Now click Save to save your work, and compile the keyboard. (Keyboard
+> Compile Keyboard, or press F7). Hopefully you’ll see it compiled
+with no error messages:
+
+![successful
+compile](../../../images/simpleTouchKeyboard_20.png)
+
+If you see a warning like:
+
+```
+Warning: line 0 warning 2092: Key " T_new_579" on layer "default", platform "phone", is a custom key but has no corresponding rule in the source.
+```
+
+This shows you forgot to enter the correct keycode for a new key you
+added. Double check your longpress popup keys for one with a “T_new_NNN”
+code, and add in the desired “U_NNNN” code.
+
+You might also see a message like
+
+```
+Error: line 0 error 405A: Key " U+0181" on "phone", layer "default" has an invalid identifier.
+```
+
+This shows you attempted to change the keycode but mistyped the code.
+I’ve gotten this more than once, because I am used to typing `U+0181`to
+refer to a Unicode value, but the correct syntax for a keycode in Keyman
+Developer is `U_0181`
+
+# Testing your keyboard
+
+Other Keyman help pages describe how you can test your keyboard:
+
+- [On a virtual device in the Chrome web browser on your
+ computer](../../test/keyboard-touch-mobile-emulator)
+- [On your own device accessing the keyboard over the
+ network](../../test/keyboard-touch-and-desktop)
+
+If your device cannot connect to your computer on the local network (for
+instance if your computer is on a wired office network and your device
+is on the wireless office network), the above technique for sharing may
+not work. You can put your keyboard in a package and share it over the
+Internet with your device, as described below.
+
+# Packaging and distributing your keyboard
+
+Another Keyman help file describes [how to build a package
+file.](../../distribute/tutorial)
+
+## BCP 47 tags for Fulfulde
+
+[Step 3](../../distribute/tutorial/step-3) of the package building
+tutorial describes adding the BCP 47 language tag to your package. When
+I came to the step of adding a tag for Fulfulde, I had to do some
+research. I naively thought "Fulfulde" was a clear enough label for the
+language. It is not, since it is a language chain rather than one
+language. The BCP 47 tag in many cases is the same as the [SIL
+Ethnologue code](https://www.ethnologue.com), so that is where I had to
+go to find the tags I needed. In the end I added four tags for four
+Fulfulde languages:
+
+![BCP 47 tags](../../../images/simpleTouchKeyboard_36.png)
+
+I also added the script designator `Latn` for Latin script. This is
+recommended for Windows, which sometimes fails to install the desktop
+keyboard if there is not a script designator on the BCP 47 tag. We
+recommend that you always put in the script tag unless there is a
+specific reason not to.
+
+Some languages have only two letters in their BCP 47 tag. For instance,
+the Hausa language has an Ethnologue code `hau` but the BCP 47 tag is
+`ha`. Keyman Developer knows about this, and will change the tag to `ha`
+if I put in the Ethnologue tag.
+
+# Next steps
+
+The two following articles on Amharic describe a more complex
+touchscreen example where quite frequently two keys are pressed to get
+one distinct character. (Amharic is a Abugida or syllabary script, where
+one character can represent a consonant and following vowel).
+
+- [Creating a touch keyboard layout for Amharic - part
+ 1](../creating-a-touch-keyboard-layout-for-amharic)
+- [Creating a touch keyboard layout for Amharic - part
+ 2](../creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/tutorial/index.md b/developer/docs/help/guides/develop/tutorial/index.md
new file mode 100644
index 00000000000..3642ddc1ed5
--- /dev/null
+++ b/developer/docs/help/guides/develop/tutorial/index.md
@@ -0,0 +1,56 @@
+---
+title: Keyboard Tutorial
+---
+
+[Step 1: Planning the Keyboard](step-1)
+
+[Step 2: Writing the Header](step-2)
+
+[Step 3: The Keyboard Header](step-3)
+
+[Step 4: The Keyboard Body](step-4)
+
+[Step 5: Rules with Context](step-5)
+
+[Step 6: Stores, 'any', and 'index'](step-6)
+
+[Step 7: Testing the Keyboard](step-7)
+
+[Step 8: Deadkeys](step-8)
+
+[Step 9: The Finished Keyboard](step-9)
+
+## Overview
+
+Welcome! In this tutorial, you will learn the basics of Keyman keyboards
+and create a simple French keyboard which can be used with any physical
+keyboard layout.
+
+The keyboard will use a basic English layout, and add some deadkeys to
+define vowel diacritics and a few other characters.
+
+While this tutorial is based on a physical keyboard such as used on a
+desktop or laptop computer, the principles of the keyboard language that
+you will learn are applicable to developing touch layouts as well.
+
+This keyboard will use Unicode. Unicode is a character encoding standard
+that supports most of the world's scripts, and includes support for
+user-defined scripts. Unicode is the accepted standard for text encoding
+in modern applications and operating systems.
+
+In Keyman Developer keyboard source files, Unicode characters are
+specified with `U+xxxx`, where `xxxx` is a four-digit hexadecimal
+number.
+
+At the bottom of each page in the tutorial, will be a link to both the
+previous page and the next page. You can use these links to work your
+way through the tutorial. You may also find links to reference
+information, which you can select to learn more about a particular
+aspect of creating Keyman keyboards.
+
+## Let's begin
+
+Let's get started! Move on to the next topic to begin the first step,
+planning the keyboard.
+
+- [Continue with Step 1: Planning the Keyboard](step-1)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/tutorial/step-1.md b/developer/docs/help/guides/develop/tutorial/step-1.md
new file mode 100644
index 00000000000..642fa916bba
--- /dev/null
+++ b/developer/docs/help/guides/develop/tutorial/step-1.md
@@ -0,0 +1,71 @@
+---
+title: Step 1: Planning the Keyboard
+---
+
+## Choosing the characters
+
+First of all, we need to decide which characters we want the keyboard to
+produce. Next, we must find out the codes used to represent them, using
+a program such as Character Map, or with the Character Map in Keyman
+Developer (go to **View**, **Character Map**).
+
+French uses the same 26 letters as English, with some additions. As our
+keyboard is based on English, we only need to work with these additional
+letters. Note that for completeness, we will design our keyboard to
+produce a few other accented vowels that are not used in French. Also,
+we want our keyboard to include the angled quotes `«` and `»`.
+
+These characters, with both uppercase and lowercase forms, are listed in
+the table below along with their Unicode codes.
+
+| | | | | | | | | |
+|-----|----------|-----|----------|-----|-----|----------|-----|----------|
+| `À` | `U+00C0` | `à` | `U+00E0` | | `Á` | `U+00C1` | `á` | `U+00E1` |
+| `È` | `U+00C8` | `è` | `U+00E8` | | `É` | `U+00C9` | `é` | `U+00E9` |
+| `Ì` | `U+00CC` | `ì` | `U+00EC` | | `Í` | `U+00CD` | `í` | `U+00ED` |
+| `Ò` | `U+00D2` | `ò` | `U+00F2` | | `Ó` | `U+00D3` | `ó` | `U+00F3` |
+| `Ù` | `U+00D9` | `ù` | `U+00F9` | | `Ú` | `U+00DA` | `ú` | `U+00FA` |
+| `Â` | `U+00C2` | `â` | `U+00E2` | | `Ä` | `U+00C4` | `ä` | `U+00E4` |
+| `Ê` | `U+00CA` | `ê` | `U+00EA` | | `Ë` | `U+00CB` | `ë` | `U+00EB` |
+| `Î` | `U+00CE` | `î` | `U+00EE` | | `Ï` | `U+00CF` | `ï` | `U+00EF` |
+| `Ô` | `U+00D4` | `ô` | `U+00F4` | | `Ö` | `U+00D6` | `ö` | `U+00F6` |
+| `Û` | `U+00DB` | `û` | `U+00FB` | | `Ü` | `U+00DC` | `ü` | `U+00FC` |
+| `Ý` | `U+00DD` | `ý` | `U+00FD` | | `Ç` | `U+00C7` | `ç` | `U+00E7` |
+| `«` | `U+00AB` | `»` | `U+00BB` | | | | | |
+
+If you are not familiar with the hexadecimal (base-16) numbering system,
+don't worry: you can use the Character Map in Keyman Developer to find
+the character you want, and then drag-and-drop or copy-and-paste its
+character code into your keyboard.
+
+Note that you must be careful to use the right character: The Unicode
+standard has many characters with the same shape as another, but a
+different meaning; an example of this is the Greek capital letter Sigma
+(`U+03A3` Σ) and the mathematical summation symbol (`U+2211`, ∑). An application
+supporting Unicode would treat these two characters differently. If in
+doubt whether a character is the right one, you can look up the
+reference tables at [www.unicode.org](http://www.unicode.org/).
+
+## Designing the layout
+
+After choosing the characters we want our keyboard to use, we must
+decide how we want the user to be able to enter them. For some
+languages, you might replace each letter on the English keyboard with a
+letter from the language. In this case, however, most of the letters are
+accented vowels, we will use two keystrokes for each: one for the
+accent, and one for the vowel.
+
+| Character | Keystrokes |
+|-------------|-------------------------------|
+| `À, à, ...` | back-quote (` ), then the vowel key. |
+| `Á, á, ...` | quote (' ), then the vowel key. |
+| `Â, â, ...` | caret (^ ), then the vowel key. |
+| `Ä, ä, ...` | double-quote (" ), then the vowel key. |
+| `Ç, ç` | quote (' ), then lower- or uppercase C. |
+| `«, »` | two less-than (<< ) or greater-than (>> ) symbols. |
+
+Now that we have decided which character to use and how the user can
+enter them, we can start to write the keyboard.
+
+- [Continue with Step 2: Writing the Header](step-2)
+- [Back to the Introduction](index)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/tutorial/step-2.md b/developer/docs/help/guides/develop/tutorial/step-2.md
new file mode 100644
index 00000000000..5867973fb39
--- /dev/null
+++ b/developer/docs/help/guides/develop/tutorial/step-2.md
@@ -0,0 +1,53 @@
+---
+title: Step 2: Writing the Header
+---
+
+## Overview of a keyboard file
+
+A keyboard file is divided into two sections: the **header** and the
+**rules** section. The header section defines the name of the keyboard,
+its bitmap, and other general settings. The rules are used to define how
+the keyboard responds to keystrokes from the user, and are divided into
+groups. We will start by writing the header.
+
+## The keyboard header
+
+The keyboard header is the first part of a keyboard; it consists of
+statements that help Keyman identify the keyboard and set default
+options for it. Each statement in the header must be on a separate line.
+While there is no technical requirement to put header statements at the
+start of a keyboard source file, keeping them there helps you identify
+them easily, and keeps them consistent with keyboard programs other
+people might write.
+
+We will begin to write the keyboard. If Keyman Developer is not already
+running, start it now. Create a new keyboard file, navigate to the
+**Layout** tab, and click on the **Code** tab at the bottom of the
+screen to switch to the Code view.
+
+Type or paste the following code into the keyboard file. This is the
+header of our keyboard file.
+
+```keyman
+c Simplified French Keyboard for Keyman 9.0
+c
+c This keyboard program uses a simplified set of keys
+c for typing French, especially for those who don't know the
+c standard French keyboard.
+c
+c NOTE: This keyboard was created from the Keyman keyboard
+c programming tutorial.
+
+store(&Version) "9.0" c This keyboard is for use with Keyman 9.0
+store(&Name) "Quick French"
+store(&Bitmap) "qfrench.ico"
+store(&MnemonicLayout) "1" c This keyboard uses a mnemonic layout.
+
+begin Unicode > use(Main)
+```
+
+An explanation of the various parts of the header follows on the next
+page.
+
+- [Continue with Step 3: The Keyboard Header](step-3)
+- [Back to Step 1: Planning the Keyboard](step-1)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/tutorial/step-3.md b/developer/docs/help/guides/develop/tutorial/step-3.md
new file mode 100644
index 00000000000..81777aec6a0
--- /dev/null
+++ b/developer/docs/help/guides/develop/tutorial/step-3.md
@@ -0,0 +1,99 @@
+---
+title: Step 3: The Keyboard Header
+---
+
+## Comments
+
+```keyman
+c Simplified French Keyboard for Keyman 9.0
+```
+
+Most of the header in this example is made up of
+[comments](/developer/language/guide/comments). A comment is used
+to make notes about the keyboard, or to provide information on the
+workings of the keyboard. The comments are readable by anyone looking at
+the source code of the keyboard.
+
+A comment always starts with a lowercase `c`, followed by one or more
+spaces, and continues to the end of the line. Keyman Developer will
+ignore comments when compiling a keyboard.
+
+Comments can take up a whole line, or can start in the middle of the
+line. The latter is useful for making short notes about individual
+lines. As you can see we have used both kinds of comments in the header.
+
+## The `&Version` store
+
+```keyman
+store(&Version) "9.0" c This keyboard is for use with Keyman 9.0
+```
+
+The [`&Version` store](/developer/language/reference/version)
+identifies the Keyman version for which this keyboard was written; this
+keyboard is for use with Keyman 9.0. The `&Version` store is an optional
+part of the keyboard header, but if present, it should be the first
+store in the file.
+
+## The `&Name` store
+
+```keyman
+store(&Name) "Quick French"
+```
+
+The [`&Name` store](/developer/language/reference/name) specifies
+a descriptive name for the keyboard, which can be up to eighty
+characters long. The name we have given to this keyboard is
+`"Quick French"`. The `&Name` store is not required but is highly
+recommended!
+
+## The `&Bitmap` store
+
+```keyman
+store(&Bitmap) "qfrench.ico"
+```
+
+The optional [`&Bitmap` store](/developer/language/reference/bitmap)
+tells Keyman which image to use for the keyboard's icon. The picture
+should be in the standard Windows .ico format, and should contain at
+least a single 16x16 pixel image. It can also contain higher resolution
+images for high resolution "High DPI" displays. If you use a modern icon
+editor, the icon can use alpha transparency.
+For this keyboard we will be using the following bitmap:
+![](../../../images/tutorial_keyboard_qfrench.gif); it is
+found in the Keyman Developer folder, under
+`Samples\Examples\qfrench.ico` - you should copy it into the same folder
+in which you will save your keyboard.
+
+## The `&MnemonicLayout` store
+
+```keyman
+store(&MnemonicLayout) "1"
+```
+
+The [`&MnemonicLayout` store](/developer/language/reference/mnemoniclayout) tells Keyman that
+the layout is meant to conform to the user's keyboard layout; for
+example, if the user presses the quote key ' on
+their keyboard (whether they are using a US English, UK English, French,
+German, Swedish, or other keyboard) it should work in the same way. The
+opposite of this is a positional layout (which is the default if this
+store omitted), which is intended for keyboards for which there is not
+necessarily a correspondence between what is printed on the physical
+keyboard and what is output when that key is pressed.
+
+## The `begin` statement
+
+```keyman
+begin Unicode > use(Main)
+```
+
+The [`begin` statement](/developer/language/reference/begin) tells
+Keyman which group of rules to process first when it receives a
+keystroke. The use of multiple groups is an advanced feature, and
+unnecessary for this tutorial, so we will use a single group, called
+`Main`. The `begin` statement is required in every keyboard, and marks the
+start of the keyboard body. The `begin` statement also tells Keyman
+which encoding to use for the keyboard. Nearly all keyboards will use
+`Unicode`, today.
+
+- [Continue with Step 4: The Keyboard Body](step-4)
+- [Back to Step 2: Writing the Header](step-2)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/tutorial/step-4.md b/developer/docs/help/guides/develop/tutorial/step-4.md
new file mode 100644
index 00000000000..e8a861ae1cd
--- /dev/null
+++ b/developer/docs/help/guides/develop/tutorial/step-4.md
@@ -0,0 +1,102 @@
+---
+title: Step 4: The Keyboard Body
+---
+
+The body of the keyboard is the most important part: it determines the
+behaviour of the keyboard. The body consists of groups, which in turn
+contain one or more rules which define the responses of the keyboard to
+certain keystrokes.
+
+## Groups
+
+There are two types of groups:
+* groups that process the keys pressed and the context.
+* groups that process only the context.
+
+For simple keyboards, the latter type of group will not be required. A group begins
+with a [`group` statement](/developer/language/reference/group), and
+ends either at the start of another group, or at the end of the keyboard
+file.
+
+We will only use one group in the Quick French keyboard, called `Main`.
+We mark the start of it with the `group` statement below. Add this line
+to the keyboard if it's not already there.
+
+```keyman
+group(Main) using keys
+```
+
+The `using keys` clause tells Keyman that this group will process
+keystrokes.
+
+## Basic Rules
+
+A rule tells Keyman the output to produce for a certain input. A rule
+consists of three parts: the **context**, the **key**, and the
+**output**.
+
+* The **context** specifies the conditions under which a rule will act. If
+what is shown in the document to the left of the cursor matches the
+context of a rule, the rule will be processed.
+* The **key** specifies which keystroke the rule will act upon.
+* The **output** determines the characters that are produced by a rule.
+The output replaces the matched context in the document.
+
+## Simple rules
+
+The simplest rules in Keyman consist of just a key and output, as below.
+(The examples in this section are just for illustration, and do not form
+part of the Quick French keyboard).
+
+```keyman
++ "a" > "ä"
+```
+
+In this rule, the key is a , and the output is `"ä"`. A simple rule
+begins with a plus sign, followed by the key, a greater-than symbol
+(suggesting "becomes"), and finally the output. As you might guess, this
+rule will change a lowercase a key typed by the
+user into **ä**.
+
+The key and output can be written as a character in single or double
+quotes (as above), or as its Unicode character code, or using a named
+constant. The rule above could have also been written any of the
+following ways, among others:
+
+```keyman
++ 'a' > 'ä'
+
++ U+0061 > U+00E4
+
+store(ADIERESIS) 'ä'
++ 'a' > $ADIERESIS
+```
+
+You can also write the key in one form and the context in another.
+
+## Rules with longer output
+
+The output of a rule is not limited to a single character. You could,
+for example, write a rule such as the following:
+
+```keyman
++ "f" > "ph"
+```
+
+This would change any f keys typed into **ph**.
+If the output of a rule consists of more than one character, you can
+write the characters in different ways if necessary, with a space
+separating each part. You can specify multiple characters in quotes, but
+if you use the Unicode codes to write the characters, you must separate
+each with a space:
+
+```keyman
++ "f" > U+0070 U+0068
+
++ "f" > U+0070 "h"
+```
+
+These rules are functionally identical to the one [further above](#toc-simple-rules).
+
+- [Continue with Step 5: Rules with Context](step-5)
+- [Back to Step 3: The Keyboard Header](step-3)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/tutorial/step-5.md b/developer/docs/help/guides/develop/tutorial/step-5.md
new file mode 100644
index 00000000000..88a76bce340
--- /dev/null
+++ b/developer/docs/help/guides/develop/tutorial/step-5.md
@@ -0,0 +1,76 @@
+---
+title: Step 5: Rules with Context
+---
+
+## Rules with Context
+
+Very often we want a keyboard to produce different output based on more
+than just the last keystroke. For example, in the Quick French keyboard,
+we want the key e to produce one of è, é,
+ë, ê, or just e, depending on what was typed immediately before
+it. To do this we must make our rules look at the context.
+
+The context is the output from previous rules; that is, the characters
+that are displayed on the screen. We can make a rule work with only
+certain context by putting this before the plus sign in the rule:
+
+```keyman
+"^" + "e" > "ê"
+```
+
+With this rule, whenever an e is typed, if it
+was preceded by a caret (^), the output will be ê. It is important
+to remember that the context consists of **output
+from previous rules**, not the previous keystrokes. To emphasize
+this point, consider the four rules below:
+
+```keyman
++ "a" > "b"
++ "b" > "c"
+"b" + "c" > "d"
+"c" + "d" > "e"
+```
+
+With these rules, typing b c would produce the
+output `cc`, and not `d`, as you might initially expect. This is because
+the key b is converted by the second rule into
+the output `c`, while the third rule expects a context of b, and not
+c; we would have to type a c to get `d`.
+
+However, if a key has no matching rule, the output will be the same as
+the key: so the output `e` will be produced for either of the inputs c d (because c by itself has no rule), and b d .
+
+## Continuing the Quick French keyboard
+
+Now we know how to create context-dependent rules, we can continue
+making the Quick French keyboard. Let's start with the rules for
+acute-accented characters, using the ANSI codes from the table we
+prepared earlier:
+
+```keyman
+c lowercase characters with acute accent
+"'" + "a" > U+00E1
+"'" + "e" > U+00E9
+"'" + "i" > U+00ED
+"'" + "o" > U+00F3
+"'" + "u" > U+00FA
+"'" + "y" > U+00FD
+
+c uppercase characters with acute accent
+"'" + "A" > U+00C1
+"'" + "E" > U+00C9
+"'" + "I" > U+00CD
+"'" + "O" > U+00D3
+"'" + "U" > U+00DA
+"'" + "Y" > U+00DD
+```
+
+We can also create similar rules for the other thirty-odd accented
+characters.
+
+As you can see, even for a simple keyboard like this we quickly end up
+with a large number of rules, which makes for clumsiness. We can make
+things simpler using stores, and the `any()` and `index()` statements.
+
+- [Continue with Step 6: Stores, `any()`, and `index()`](step-6)
+- [Back to Step 4: The Keyboard Body](step-4)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/tutorial/step-6.md b/developer/docs/help/guides/develop/tutorial/step-6.md
new file mode 100644
index 00000000000..95ade8767c6
--- /dev/null
+++ b/developer/docs/help/guides/develop/tutorial/step-6.md
@@ -0,0 +1,116 @@
+---
+title: Step 6: Stores, any(), and index()
+---
+
+When we have many similar rules, as in the last example on the previous
+page, we can group them together into one rule by using stores. A store
+is a set of characters that are grouped under a single name. Stores are
+used in rules with the [`any()`](/developer/language/reference/any) and
+[`index()`](/developer/language/reference/index) statements. We create a
+store with a [`store()` statement](/developer/language/reference/store):
+
+```keyman
+store(vowels) "aeiou"
+```
+
+This creates a store called `vowels`, which contains the five lowercase
+vowels.
+> #### Note:
+We could also have written the content of the store
+using ANSI or Unicode character codes, in the same way as the output.
+
+The `any()` statement is used to match a character from a specific
+store. For example, the following rule will replace any vowel with a
+period, when used with the store above:
+
+```keyman
++ any(vowels) > "."
+```
+
+The `any()` statement can be used in the context or in the key part of a
+rule. It cannot be used in the output.
+
+The second statement that is used with stores is the `index()`
+statement. It is usually used in the output of a rule, and will output
+the character from a particular store at the same position as the
+character matched by a specified `any()` statement. This is best shown
+with an example; this rule will convert all input to uppercase:
+
+```keyman
+store(lowercase) "abcdefghijklmnopqrstuvwxyz"
+store(uppercase) "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
++ any(lowercase) > index(uppercase,1)
+```
+
+When a letter, such as j is typed, the `any()`
+statement finds its position in the `lowercase` store; the `index()`
+statement then gets this index from the `any()` statement, and outputs
+the character at the same position in the `uppercase` store, in this
+case `J`.
+
+The `index()` statement has two parts: the store from which it takes the
+output character, and the offset of the `any()` statement that it gets
+the character position from. This offset is found by counting the
+characters and statements in the context and key parts of the rule up to
+the `any()` statement. Again, a few examples may help to illustrate
+this:
+
+```keyman
+"a" + any(somestore) > index(otherstore,2) c The 'any' statement
+ c is character #2
+"ab" any(somestore) + "c" > index(otherstore,3) c The 'any' statement
+ c is character #3
+
+c Here the 'index' statement references the second 'any' statement used,
+c which is character #4
+U+0041 any(somestore) "B" + any(otherstore) > index(thirdstore,4)
+
+c You can have multiple 'index' statements in the output, which can
+c reference the same or different 'any's
+any(store1) + any(store2) > index(store1,2) index(store2,1) index(store3,2)
+```
+
+## Using stores in the Quick French keyboard
+
+We can now reduce the number of rules needed for the Quick French
+keyboard by using stores. We will make five stores: one for the
+unaccented vowels, and one each for vowels with acute accents, grave
+accents, circumflexes, and diereses. For clarity, the `group()`
+statement is repeated below:
+
+```keyman
+group(Main) using keys
+
+store( plainvowels ) 'a' 'e' 'i' 'o' 'u' 'A' 'E' 'I' 'O' 'U'
+store( acutevowels ) U+00E1 U+00E9 U+00ED U+00F3 U+00FA U+00C1 U+00C9 U+00CD U+00D3 U+00DA
+store( gravevowels ) U+00E0 U+00E8 U+00EC U+00F2 U+00F9 U+00C0 U+00C8 U+00CC U+00D2 U+00D9
+store( circumvowels ) U+00E2 U+00EA U+00EE U+00F4 U+00FB U+00C2 U+00CA U+00CE U+00D4 U+00DB
+store( dresisvowels ) U+00E4 U+00EB U+00EF U+00F6 U+00FC U+00C4 U+00CB U+00CF U+00D6 U+00DC
+
+"'" + any( plainvowels ) > index( acutevowels, 2 )
+"`" + any( plainvowels ) > index( gravevowels, 2 )
+"^" + any( plainvowels ) > index( circumvowels, 2 )
+'"' + any( plainvowels ) > index( dresisvowels, 2 )
+```
+
+This is far clearer than the long list of rules that we used earlier.
+Obviously we should add one or two more ordinary rules to produce uppercase
+and lower-case ç, ý, and also the angled quotes « and ». Then we
+will have almost finished the keyboard:
+
+```keyman
+"'" + "y" > U+00FD c Acute-accented Y
+"'" + "Y" > U+00DD
+
+"'" + "c" > U+00E7 c C-cedilla
+"'" + "C" > U+00C7
+
+"<" + "<" > U+00AB c Angled quotes
+">" + ">" > U+00BB
+```
+
+All we need to do now is to test the keyboard.
+
+- [Continue with Step 7: Testing the Keyboard](step-7)
+- [Back to Step 5: Rules with Context](step-5)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/tutorial/step-7.md b/developer/docs/help/guides/develop/tutorial/step-7.md
new file mode 100644
index 00000000000..2432bb76f4a
--- /dev/null
+++ b/developer/docs/help/guides/develop/tutorial/step-7.md
@@ -0,0 +1,52 @@
+---
+title: Step 7: Testing the Keyboard
+---
+
+# Compiling the Keyboard
+
+Before we can test the keyboard, we must compile it. Choose
+Keyboard, Compile
+Keyboard or press F7 to compile the
+keyboard. The Message window will display the results of the
+compilation; if you have no typing errors, the keyboard should compile
+successfully.
+
+If there are any mistakes, an error message will be displayed in the
+Message window, which will tell you the line on which the error
+occurred.
+
+## Compiling the Keyboard
+
+After compiling the keyboard successfully, we can start testing it.
+Choose Debug,
+Start Debugging or press
+F5 to begin testing. The Keyboard debug window
+will appear.
+
+Now we test that all the rules operate as expected. To test the rules,
+we type the keystrokes that will give us the output; for example, we can
+type a quote ' followed by one of
+A , E ,
+I , O ,
+U , or Y to test the
+uppercase acute-accented vowels. Similarly we can test the other
+accents, and C-cedilla (`Ç`) and the angled quotes. If the rules are
+correct, all this should work as we wanted.
+
+Testing the rules in isolation like this will show if the rules are
+correct or not, but won't show other possible errors that might occur in
+everyday usage of the keyboard. For example, look at what happens if you
+type the following quote:
+
+> `'Alors Alice demande, "Où est mon chat ?"'`
+
+As you can see, it comes out incorrectly as:
+
+> `Álors Alice demande, Öù est mon chat ?"'`
+
+The problem occurs when we have a word in quotes that begins with a
+vowel: the keyboard will convert it to an accented vowel. We will need
+to come up with a solution to this problem.
+
+- [Continue with Step 8: Deadkeys](step-8)
+- [Back to Step 6: Stores, `any()`, and `index()`](step-6)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/tutorial/step-8.md b/developer/docs/help/guides/develop/tutorial/step-8.md
new file mode 100644
index 00000000000..52f532ce76d
--- /dev/null
+++ b/developer/docs/help/guides/develop/tutorial/step-8.md
@@ -0,0 +1,78 @@
+---
+title: Step 8: Deadkeys
+---
+
+## Solving the Problem
+
+Probably the easiest solution to the problem which we encountered on the
+last page is to design the keyboard so that the user types a quote key
+twice when they want to produce a quote, but once when they want to use
+it as an accent. However, we cannot simply use the rule:
+
+```keyman
+"'" + "'" > "'"
+```
+
+This will certainly produce a single quote after it's typed twice;
+however it will still produce the same error, because the following
+keystroke will continue to swallow the quote character from the context.
+We need to distinguish between the output of this rule, when the user
+wants a quote, and that of a single quote press, when the user wants to
+place an accent on a vowel. To implement this behaviour, we use
+[deadkeys](/developer/language/reference/deadkey).
+
+## Deadkeys
+
+A deadkey is like a character that is used in the context or output but
+never appears on the screen. We use deadkeys like this:
+
+```keyman
++ "'" > deadkey(quote)
+
+c Handle acute accents
+deadkey(quote) + any( plainvowels ) > index( acutevowels, 2 )
+
+c Handle a single quote
+deadkey(quote) + "'" > "'"
+```
+
+Note that for the sake of convenience, a deadkey can also be written in
+a short form:
+
+```keyman
+dk(quote) c This is identical to deadkey(quote)
+```
+
+Type the three rules above in place of the existing rule for acute
+accents in the keyboard so far. If you test the keyboard now, you will
+find that the error no longer occurs. This is because accented vowels
+are only produced after the deadkey, and no deadkey is output if the
+user types a quote key twice.
+
+But we've introduced another difference to the keyboard now: the quote
+is no longer displayed before you type the vowel. This is because we are
+converting the quote to a deadkey. If we prefer, we can still
+distinguish between rules in the above manner and display the quote, if
+we just add a quote before the deadkey, like this:
+
+```keyman
++ "'" > "'" dk(quote)
+
+c Handle acute accents
+"'" dk(quote) + any( plainvowels ) > index( acutevowels, 3 )
+
+c Handle a single quote
+"'" dk(quote) + "'" > "'"
+```
+
+However, we will not use this technique for the Quick French keyboard.
+
+Now we can make changes so that all the other accents use deadkeys as
+well, for consistency, and add rules to output the accent character by
+typing the key twice.
+
+The Quick French keyboard is now complete. The full source is on the
+next page.
+
+- [Continue with Step 9: The Finished Keyboard](step-9)
+- [Back to Step 7: Testing the Keyboard](step-7)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/tutorial/step-9.md b/developer/docs/help/guides/develop/tutorial/step-9.md
new file mode 100644
index 00000000000..03e00a27870
--- /dev/null
+++ b/developer/docs/help/guides/develop/tutorial/step-9.md
@@ -0,0 +1,66 @@
+---
+title: Step 9: The Finished Keyboard
+---
+
+## The Quick French Keyboard
+
+Here is the completed keyboard:
+
+```keyman
+c Simplified French Keyboard for Keyman 9.0
+c
+c This keyboard program uses a simplified set of keys
+c for typing French, especially for those who don't know the
+c standard French keyboard.
+c
+c NOTE: This keyboard was created from the Keyman keyboard
+c programming tutorial.
+
+store(&Version) "9.0" c This keyboard is for use with Keyman 9.0
+store(&Name) "Quick French"
+store(&Bitmap) "qfrench.ico"
+store(&MnemonicLayout) "1" c This keyboard uses a mnemonic layout.
+
+begin Unicode > use(Main)
+
+group( Main ) using keys
+
+c Store the upper and lowercase vowels with different accents
+store( plainvowels ) 'a' 'e' 'i' 'o' 'u' 'A' 'E' 'I' 'O' 'U'
+store( acutevowels ) U+00E1 U+00E9 U+00ED U+00F3 U+00FA U+00C1 U+00C9 U+00CD U+00D3 U+00DA
+store( gravevowels ) U+00E0 U+00E8 U+00EC U+00F2 U+00F9 U+00C0 U+00C8 U+00CC U+00D2 U+00D9
+store( circumvowels ) U+00E2 U+00EA U+00EE U+00F4 U+00FB U+00C2 U+00CA U+00CE U+00D4 U+00DB
+store( dresisvowels ) U+00E4 U+00EB U+00EF U+00F6 U+00FC U+00C4 U+00CB U+00CF U+00D6 U+00DC
+
+c Output deadkeys only for the accent keys pressed
++ "'" > dk(quote) c Quote for acute accent
++ "`" > dk(bkquote) c Backquote for grave accent
++ "^" > dk(caret) c Caret for circumflex
++ '"' > dk(dbquote) c Double-quote for dieresis
+
+c Rules for accented vowels
+dk(quote) + any( plainvowels ) > index( acutevowels, 2 )
+dk(bkquote) + any( plainvowels ) > index( gravevowels, 2 )
+dk(caret) + any( plainvowels ) > index( circumvowels, 2 )
+dk(dbquote) + any( plainvowels ) > index( dresisvowels, 2 )
+
+c Rules for other characters
+dk(quote) + "y" > U+00FD c Acute-accented Y
+dk(quote) + "Y" > U+00DD
+
+dk(quote) + "c" > U+00E7 c C-cedilla
+dk(quote) + "C" > U+00C7
+
+"<" + "<" > U+00AB c Angled quotes
+">" + ">" > U+00BB
+
+c Rules for the accent character itself (type it twice)
+dk(quote) + "'" > "'" c Quote
+dk(bkquote) + "`" > "`" c Backquote
+dk(caret) + "^" > "^" c Caret
+dk(dbquote) + '"' > '"' c Double-quote
+
+c End of keyboard
+```
+
+- [Back to Step 8: Deadkeys](step-8)
\ No newline at end of file
diff --git a/developer/docs/help/guides/distribute/index.md b/developer/docs/help/guides/distribute/index.md
new file mode 100644
index 00000000000..7b84bf472fd
--- /dev/null
+++ b/developer/docs/help/guides/distribute/index.md
@@ -0,0 +1,10 @@
+---
+title: Distributing keyboards
+---
+
+- [Package development tutorial](tutorial) (Keyman Desktop, Keyman for macOS, Keyman for mobile apps)
+- [Distribute keyboards to Keyman applications](packages)
+
+## See also
+
+- [Keyman Engine](/developer/engine/)
\ No newline at end of file
diff --git a/developer/docs/help/guides/distribute/install-kmp-android.md b/developer/docs/help/guides/distribute/install-kmp-android.md
new file mode 100644
index 00000000000..bdba196c908
--- /dev/null
+++ b/developer/docs/help/guides/distribute/install-kmp-android.md
@@ -0,0 +1,78 @@
+---
+title: Installing Custom Keyboards to your Android Device
+---
+
+## Overview
+
+In [Keyman for Android 10.0](/products/android/10.0/), we added the ability to
+easily download/share custom keyboards to your Android device.
+
+## Installing a custom keyboard
+
+To install a custom keyboard, you will need a link to download the
+keyboard package. If you have already copied the kmp file onto the
+Android device, skip to Step 2.
+
+If you want to build and dev your own keyboard, you will need some
+technical skills for this, and can read our full step-by-step
+instructional on how to do this [here](packages).
+
+Once you have a website or email with a link to the keyboard package,
+follow these steps on your Android device to download and install the
+keyboard package into the Keyman for Android application:
+
+## Step 1) Click the link to save your custom keyboard package file
+
+The link in this example is for Khmer Angkor keyboard.
+
+![](../../images/dist-url-screen-ap.png)
+
+## Step 2) Add Keyboard from Device
+
+Once the KMP file is on your device, you will need to browse to the KMP
+file and select it. From the Keyman menu, select "Settings". From the
+Keyman Settings menu, select "Add Keyboard from Device".
+
+![](../../images/settings-language-ap.png)
+
+The device will launch a file browser where you'll browse to the
+directory of your KMP file. A common places to look is the "Downloads"
+folder.
+
+![](../../images/dist-file-browser-ap.png)
+
+Selecting the KMP file should bring you to Step 3)
+
+## Step 3) Grant Keyman for Android access to storage (Android 6.0+ only)
+
+On Android 6.0 (Marshmallow) and higher, mobile apps need to request
+permission to access storage. Keyman for Android needs access to read
+storage for installing the KMP file. At the dialog, select "ALLOW". Once
+authorized, Keyman for Android won't need to ask for storage permission
+again, unless the user revokes or uninstalls the app.
+
+![](../../images/dist-storage-permission-ap.png)
+
+Older versions of Android grant Storage permissions at app installation
+time, so those users can skip this step.
+
+## Step 4) Keyboard Package welcome screen
+
+Keyman for Android will parse the metadata in the package. If the
+keyboard package includes a "welcome.htm" file, this will be displayed
+at the confirmation to install the keyboard package
+
+![](../../images/dist-welcome-ap.png)
+
+If "welcome.htm" is not included, a generic page with the package ID and
+package version will be shown.
+
+Click the left "Install" button to install the entire keyboard package
+
+## Step 5) The keyboard is successfully installed!
+
+All the keyboards in the package are installed as a group. In this
+example, the package only has the "Khmer Angkor" keyboard, so it becomes
+the active keyboard
+
+![](../../images/dist-install1-ap.png)
\ No newline at end of file
diff --git a/developer/docs/help/guides/distribute/install-kmp-ios.md b/developer/docs/help/guides/distribute/install-kmp-ios.md
new file mode 100644
index 00000000000..de4d3a64cb5
--- /dev/null
+++ b/developer/docs/help/guides/distribute/install-kmp-ios.md
@@ -0,0 +1,53 @@
+---
+title: Installing Custom Keyboards to your iPhone or iPad
+---
+
+## Overview
+
+In version 10 of Keyman for iPhone and iPad, we added the ability to easily download custom keyboards to your iOS device.
+
+## Installing a custom keyboard
+
+To install a custom keyboard, you will first need a link to the keyboard
+package on a website or in your email.
+
+If you want to build and dev your own keyboard, you will need some
+technical skills for this, and can read our full step-by-step
+instructional on how to do this [here](packages).
+
+Once you have a website or email with a link to the keyboard package,
+follow these steps on your iPhone or iPad to download and install the
+keyboard package into the Keyman for iPhone and iPad application:
+
+## Step 1) Click the link to your custom keyboard package file
+
+The link in this example is for GFF Amharic 7 keyboard.
+
+![](../../images/dist-url-screen-i.png)
+
+Safari will display an option to open the KMP file with Keyman
+
+![](../../images/dist-kmp-open-i.png)
+
+## Step 2) Click Open in "Keyman"
+
+Keyman for iPhone and iPad will parse the metadata in the package. If
+the keyboard package includes a "welcome.htm" file, this will be
+displayed at the confirmation to install the keyboard package
+
+![](../../images/dist-welcome-i.png)
+
+If "welcome.htm" is not included, a generic page with the package ID and
+package version will be shown.
+
+## Step 3) Click the top right "Install" button
+
+The keyboard from the keyboard package is successfully installed!
+
+![](../../images/dist-kmp-success-i.png)
+
+All the keyboards in the package are installed as a group. In this
+example, the package only has the "GFF Amharic 7" keyboard, so it
+becomes the active keyboard
+
+![](../../images/dist-install1-i.png)
diff --git a/developer/docs/help/guides/distribute/packages.md b/developer/docs/help/guides/distribute/packages.md
new file mode 100644
index 00000000000..e3382220a30
--- /dev/null
+++ b/developer/docs/help/guides/distribute/packages.md
@@ -0,0 +1,131 @@
+---
+title: Distribute keyboards to Keyman Applications
+---
+
+## Overview
+
+Keyboard package files contain one or more keyboards, along with readme
+files, fonts (if your keyboard requires a custom font), and any other
+files you wish to include. You should create a package file to bundle
+your keyboard with fonts and help into a simple, single file that is
+easy for an end-user to install.
+
+Internally, as well as your own files, the package file will contain
+metadata files "kmp.inf" and "kmp.json", which list the details Keyman
+needs to install the package. The package file is a ZIP compatible
+archive.
+
+> ### Tip
+You can distribute keyboards and lexical models in package files, but
+you can't include both in the same package file.
+
+#### Keyman Desktop
+
+Keyman Desktop can install package files, including installing fonts,
+creating Start Menu shortcuts, and adding appropriate registry entries
+for uninstallation.
+
+On Windows, the context menu for a package file has one additional
+entry: "Install".
+
+#### Keyman for macOS
+
+Keyman for macOS can install package files with fonts and keyboards, and
+shortcuts in the package will be available through the keyboard's entry
+in Keyman Configuration.
+
+#### Keyman for Android and Keyman for iPhone and iPad
+
+Keyman mobile applications can install the same package files as Keyman
+Desktop, as long as the package includes keyboards for touch layouts.
+
+## Package file contents
+
+A package can have a variety of different files contained within. The
+following files and file types are recognized by the package installer:
+
+\*.kmx (Desktop and macOS only)
+: Keyboard files. Each of these will be installed. Keyman
+ Configuration will not allow installation or uninstallation of a
+ single keyboard from a package. They will always be treated as a
+ group for installation and uninstallation.
+
+\*.kvk (Desktop and macOS only)
+: On Screen Keyboard files, associated with each keyboard file.
+
+\*.js (mobile only)
+: Touch layout Keyboard files. When Keyman mobile applications install
+ a keyboard package, all included keyboards will be installed as a
+ group. With Keyman Developer 10, the keyboard version information is
+ in kmp.json, and no longer within the JS file names.
+
+welcome.htm
+: Introductory help for the keyboard, HTML format. This will normally
+ be displayed when the package is installed by the user, and is also
+ the entry point for help when accessed via Keyman's help system or
+ Keyman Configuration.
+
+readme.htm
+: Displayed before a package is installed, together with brief
+ metadata about the package, to allow the user to determine if they
+ wish to continue installation of the package.
+
+kmp.inf (Legacy versions of Keyman)
+: A Windows .ini format file that lists each of the files in the
+ package, together with metadata.
+
+kmp.json
+: A JSON format file contains metadata for the keyboard package such
+ as package version, keyboard versions, and lists each of the files
+ in the package. For more explanation of the structure of the JSON
+ file, please read the
+ [metadata](../../reference/file-types/metadata) documentation.
+
+\*.ttf, \*.otf, \*.ttc
+: Truetype font files that will be installed with the package, and
+ uninstalled when the package is removed. On mobile, these fonts will
+ be available only within the Keyman app and the on-screen-keyboard,
+ not in other apps.
+
+## Share the keyboard package file
+
+> ### Tip
+Read [Step 2: Organizing the Keyboard Files](../../../keyboards/github/step-2)
+to know which files to include or exclude in the keybord folder.
+
+Once the keyboard package .kmp file is created, you can share them via
+external storage devices (USB drive, SD card, etc). You can also share
+the package file via a cloud storage system (Google Drive, Dropbox or
+similar service), then share the link to your device via text, note or
+email. Alternatively, you can upload the .kmp file to a public facing
+website. For this example, the keyboard package for Khmer Angkor is
+being uploaded:
+
+1. khmer_angkor.kmp (the keyboard package .kmp file)
+
+## Putting the keyboard package on a website
+
+Once all the files have been uploaded, you will need to provide a link
+to the keyboard package .kmp file for your device to download and
+install. This can either be a link on a web page, or a link in an email.
+In this tutorial, a very simple .html web page with a link to the
+khmer_angkor.kmp file is created:
+
+``` markup
+
+
+
+
+ Custom Keyboard URL
+
+
+```
+
+The link must be in the format `http://` or `https://`
+
+Upload the web page to your public facing website. Once done, you can
+install the keyboard package onto your mobile devices by following these
+steps:
+
+- [Installing custom keyboards to your iPhone or iPad](install-kmp-ios)
+- [Installing custom keyboards to your Android device](install-kmp-android)
diff --git a/developer/docs/help/guides/distribute/tutorial/index.md b/developer/docs/help/guides/distribute/tutorial/index.md
new file mode 100644
index 00000000000..ec57e57e100
--- /dev/null
+++ b/developer/docs/help/guides/distribute/tutorial/index.md
@@ -0,0 +1,36 @@
+---
+title: Package Tutorial
+---
+
+[Step 1: What do we include?](step-1)
+
+[Step 2: Creating Additional Files for the Package](step-2)
+
+[Step 3: Creating a package and adding files](step-3)
+
+[Step 4: Filling in package details](step-4)
+
+[Step 5: Shortcuts](step-5)
+
+[Step 6: Compiling, testing and distributing a Package](step-6)
+
+## Overview
+
+Welcome! In this tutorial, you will learn how to create a
+[package](../../../reference/file-types/kmp). A package is a collection
+of files, compressed into a single file, just like a ZIP file. It is
+designed to make installation of keyboards, fonts and related
+documentation straightforward for the end user. When done, the package
+can be installed into Keyman Desktop through a simple dialog. Keyman for
+mobile apps can also download and install keyboards through the keyboard
+package.
+
+We will be creating a Khmer Angkor package. You can make your own
+package based on the Quick French keyboard from the [keyboard tutorial](../../develop/tutorial).
+
+### Let's begin
+
+Let's get started! Move on to the next topic to begin the first step,
+choosing what to include in the package.
+
+[Step 1: What do we include?](step-1)
\ No newline at end of file
diff --git a/developer/docs/help/guides/distribute/tutorial/step-1.md b/developer/docs/help/guides/distribute/tutorial/step-1.md
new file mode 100644
index 00000000000..a18fce49ac9
--- /dev/null
+++ b/developer/docs/help/guides/distribute/tutorial/step-1.md
@@ -0,0 +1,35 @@
+---
+title: Step 1: What do we include?
+---
+
+What is the purpose of a package? To make installation of a keyboard,
+fonts, documentation, and on screen keyboard as straightforward as
+possible for the end user.
+
+We need to keep this goal in mind as we work on all the aspects of a
+package.
+
+A good package will:
+
+1. Collect the keyboard and font files together for simple installation
+2. Include Start Menu items for documentation
+
+A great package will also:
+
+1. Include a 'readme' visible before install.
+2. Include a 'welcome.htm' file which is displayed after install.
+3. Include an On Screen Keyboard if relevant.
+4. Include documentation on use of the keyboard.
+
+Packages uploaded to the Tavultesoft website will get basic icon ratings
+reflecting their ease of install/use:
+
+- ![](../../../images/tutorial_package_includesfonts.gif) includes fonts
+- ![](../../../images/tutorial_package_includesdocs.gif) includes documentation
+- ![](../../../images/tutorial_package_includesosk.gif) includes on screen keyboards
+- ![](../../../images/tutorial_package_includeswelcome.gif) includes welcome.htm
+
+In general, the more icons that a package earns, the easier that end
+users will find it to start using.
+
+[Step 2: Creating Additional Files for the Package](step-2)
\ No newline at end of file
diff --git a/developer/docs/help/guides/distribute/tutorial/step-2.md b/developer/docs/help/guides/distribute/tutorial/step-2.md
new file mode 100644
index 00000000000..17dc613ba8d
--- /dev/null
+++ b/developer/docs/help/guides/distribute/tutorial/step-2.md
@@ -0,0 +1,127 @@
+---
+title: Step 2: Creating Additional Files for the Package
+---
+
+You should create or select some additional files for a package. Below
+are a list of the typical files that you would create for or include in
+a package.
+
+readme.htm
+
+: A short description of the package, its use restrictions, and what
+ it includes. Try to keep the readme under 10 lines long. The readme
+ is displayed in the package install dialog and should be an html
+ file for optimal formatting.
+
+ > ### Tip
+ As the package has not been installed at this stage, and you are using fonts that may not yet be available on the target device, you should use CSS @font-face declarations to ensure that the text is readable on all devices.
+
+ Create the HTML file in any HTML editor.
+
+ > ### Tip
+ If using Microsoft Word, choose HTML (clean) when saving to create a smaller file.
+
+ Note, if your HTML editor puts images into a subfolder, you will need to edit the HTML source so that all files are in the same folder -- the package builder will not maintain subfolders. You can easily edit the HTML source in Keyman Developer.
+
+ Also, if your welcome or readme files use embedded external images, stylesheets, javascript or other files, you will need to add these files to your package as well.
+
+welcome.htm
+
+: When including an introductory help file in your package, you must
+ name the file welcome.htm. This file will be detected during install
+ and displayed (in a window roughly two thirds of the user's screen
+ width) after the package install completes successfully. Make sure
+ that you design your HTML file so that it can be resized to fit the
+ user's screen - avoid extra wide tables or wide fixed width
+ elements.
+
+ At this stage of the installation, fonts in the package have been installed, so you can include text that uses those fonts.
+
+ Welcome.htm will also be accessible after installation from Keyman Configuration under the Package Options menu, and from the Keyboard Help item and On Screen Keyboard toolbar button.
+
+ The intention of welcome.htm is to provide instructions on getting started with your keyboards.
+
+ > ### Tip
+ Useful information to include in welcome.htm is:
+ - key sequences - especially for characters that may not be immediately obvious.
+ - names of fonts in the package.
+ - names of keyboards in the package.
+ - links to additional help on your website or more extensive documentation files in the package.
+ - a link to an official distribution site for your package (even
+ if it is the Keyman website) - so that users know where to find
+ the latest version of your package.
+
+ You should avoid including instructions for the use of Keyman
+ itself - although a basic "click the Keyman icon and choose Quick
+ French" would be helpful.
+
+ > ### Tip
+ If you want links to your website to open in the user's preferred
+ browser, preface the href link with `link:`, e.g.
+ `website `
+ The `link:` sceheme will open the referred file in the default
+ application - that is, a web browser for URLs and links, Notepad for
+ .txt files, Adobe Reader for PDFs. You can use `link:` to open any
+ of the files in the package, e.g. `link:docs.pdf` will open the file
+ docs.pdf in Adobe Reader or the default PDF reader on the system.
+
+ To save you the effort of writing a welcome and readme file for the
+ Quick French example, we have placed some in the Samples/QFrench
+ folder.
+
+ If you create documents in other formats, for example PDF or
+ printable documentation, you should link to that in the welcome.htm.
+
+ > ### Tip
+ You can include multiple "welcome.htm" files for different languages
+ by appending a hyphen and the BCP 47 language code to the filename,
+ for example welcome-fr.htm for French.
+
+Fonts
+
+: A font is the single most important item to include with a keyboard
+ – if the characters of the language you are supporting are not in
+ fonts included with target operating systems. Installing fonts is
+ tedious, so make sure that your users don't have to locate and
+ install fonts themselves!
+
+ .TTF, .OTF, and .TTC fonts will be installed by the package
+ installer, and uninstalled when the package is uninstalled. A list
+ of the fonts installed is displayed in the Install Package dialog.
+
+Keyboards
+
+: Add the .kmx compiled Keyman keyboards to the package. You can add
+ multiple keyboards to the package, but be judicious.
+
+ > ### Tip
+ Don't forget to add the On Screen keyboard .KVK file associated with
+ your keyboard to the package. KVK files are not compiled into the
+ .KMX file -- although the .ICO/.BMP is.
+
+ Add the .js compiled Keyman touch layout keyboards to the package.
+
+Documentation
+
+: The two preferred documentation formats are HTML and PDF. You should
+ avoid .DOC, .RTF, and other formats -- .DOC files in particular are
+ not recommended due to the possibility of macro viruses.
+
+ Remember that HTML files can be displayed on any computer without
+ additional software. PDF files require Adobe Reader or a compatible
+ PDF viewer application. You may choose to include both and HTML
+ documentation - PDF documents often print better than HTML
+ documents, but HTML documentation is more accessible and translates
+ better to on-screen or web use.
+
+ > ### Tip
+ If you use HTML, don't forget to also add all the included files
+ such as images and stylesheets!
+
+Splash Image
+: The splash image is a 140x250 pixel image that is displayed when the
+ package is installed, at the left of the Package Install dialog.
+ Including a splash image makes your package look more professional
+ and polished, so a splash image is recommended!
+
+[Step 3: Creating a package and adding files](step-3)
\ No newline at end of file
diff --git a/developer/docs/help/guides/distribute/tutorial/step-3.md b/developer/docs/help/guides/distribute/tutorial/step-3.md
new file mode 100644
index 00000000000..657f2cd02b5
--- /dev/null
+++ b/developer/docs/help/guides/distribute/tutorial/step-3.md
@@ -0,0 +1,31 @@
+---
+title: Step 3: Creating a package and adding files
+---
+
+We are finally ready to open up the Package Editor and create the new
+package.
+
+- In the Project Window in Keyman Developer, click the **Packages** tab, and click **New Package...**. Enter the path and filename of the package you are creating.
+
+> ### Tip
+This tutorial makes a package named `khmer_angkor.kps`, but you could call yours `qfrench.kps`.
+
+A package source file will have the extension .kps, and will be compiled in a file with extension .kmp.
+
+After you click OK, the Package Editor will open with the Files tab visible.
+
+![](../../../images/tutorial_distribute_keyboard_3_files.png)
+
+- In the Files tab, click **Add...** to add
+ all the files we discussed in the previous step to the package. You
+ can add multiple files at once, and from multiple folders. When the
+ package is compiled, all the files will be placed in the same folder
+ within the package.
+- If your package includes .js touch layout keyboards, you can
+ associate fonts and languages in the Keyboards tab. A valid [BCP 47 language tag](../../../reference/bcp-47) must be set or the keyboard will not install on your mobile device.
+
+![](../../../images/tutorial_distribute_keyboard_3.png)
+
+You could stop here. This would be a completely valid package, but it would not be as good as it could be. So let's continue on to the next step, and fill in some descriptions of the package.
+
+[Step 4: Filling in package details](step-4)
\ No newline at end of file
diff --git a/developer/docs/help/guides/distribute/tutorial/step-4.md b/developer/docs/help/guides/distribute/tutorial/step-4.md
new file mode 100644
index 00000000000..67c55fe66b8
--- /dev/null
+++ b/developer/docs/help/guides/distribute/tutorial/step-4.md
@@ -0,0 +1,63 @@
+---
+title: Step 4: Filling in package details
+---
+
+Click on the Details tab in the Package Editor. You should fill in as
+many details as you can on this page.
+
+![](../../../images/tutorial_distribute_keyboard_details.png)
+
+Package Name
+
+: The Package Name will be displayed in the package install dialog,
+ and within Keyman Desktop Configuration, wherever the package is
+ referred to.
+
+Readme File
+
+: Select the readme file from the list of files you added in the
+ previous step. Remember that it should be a HTML file for optimum
+ clarity.
+
+Version
+
+: A version number for the package is important - it helps the your
+ users know that they are using the most recent update of your
+ package. The version format you should use is `1.0`. When making a
+ major change to your package or keyboards in your package, increment
+ the first part and set the second part to, e.g. `2.0`; when making a
+ bug fix or a minor update, increment the second part, e.g. `1.1`.
+
+Version numbers should be in the form `major.minor[.subversion]`.
+ Subversion is optional but is helpful for small bug fix releases.
+ Each of the sections of the version should be an integer. Keyman
+ Desktop does integer comparisons on the version numbers, so, for
+ example, version `2.01` is regarded as older than version `2.2`.
+ Alphabetic or date formats should be avoided as the installer for
+ the keyboard cannot determine which version is older reliably.
+
+Copyright
+
+: Enter copyright details for your package and keyboards. Keep this
+ reasonably short or it won't be clear for end users.
+
+Author
+
+: Enter your name or the name of your company.
+
+Email
+
+: Enter a contact email address where package users can contact you.
+ If you don't want to be contacted via email, leave this field empty
+
+Website
+
+: Enter the name of the website where you will have information about
+ this keyboard. If you want to host it on keyman.com, you could enter
+ `http://www.keyman.com/`
+
+Image file
+: Select the splash image file that you created in Step 2 from the
+ list of files in your package.
+
+[Step 5: Shortcuts](step-5)
diff --git a/developer/docs/help/guides/distribute/tutorial/step-5.md b/developer/docs/help/guides/distribute/tutorial/step-5.md
new file mode 100644
index 00000000000..eb24d70f133
--- /dev/null
+++ b/developer/docs/help/guides/distribute/tutorial/step-5.md
@@ -0,0 +1,49 @@
+---
+title: Step 5: Shortcuts
+---
+
+Click on the Shortcuts tab in the Package Editor.
+
+![](../../../images/tutorial_distribute_keyboard_shortcuts.png)
+
+While it may be a good idea to include Start Menu shortcuts for your
+package, on recent versions of Windows these are not very visible to end
+users. This means that you should ensure that all instructions are
+available through the **welcome.htm** file as well. The Start Menu
+shortcuts were designed originally for Windows-based keyboard packages
+and work slightly differently in macOS.
+
+You should consider adding the following items to the Start Menu:
+
+- Documentation shortcuts
+- Welcome.htm shortcut
+
+> ### Note
+While Keyman Developer 8.0 and earlier versions of the documentation
+advised including an uninstall shortcut to the package, this is no
+longer recommended. Users should uninstall your package from Keyman
+Configuration. Adding an uninstall shortcut to the package adds
+confusion to the Start Menu search on Windows, and is not compatible
+with macOS keyboards.
+
+Click **New** to add a new shortcut to the
+package. Enter a description for the shortcut, and select the program or
+file to start from the Program list. Four predefined program entries
+(Start Product), (Product Configuration), (Product Help) and (About
+Product) will be translated into the appropriate shortcuts to start
+Keyman Desktop tasks as described.
+
+> ### Note
+In Keyman 6 and earlier, the predefined targets $KEYMAN\keyman.exe and
+$KEYMAN\kmshell.exe were available. These are translated to (Start
+Product) and (Product Configuration), respectively, in Keyman Desktop
+7.0 and later versions.
+
+
+
+> ### Tip
+Packages are not listed in the Control Panel Add/Remove Programs applet
+with Keyman Desktop 7.0 and later versions. Packages can be uninstalled
+through the Start Menu shortcut or from Keyman Desktop Configuration.
+
+[Step 6: Compiling, testing and distributing a Package](step-6)
\ No newline at end of file
diff --git a/developer/docs/help/guides/distribute/tutorial/step-6.md b/developer/docs/help/guides/distribute/tutorial/step-6.md
new file mode 100644
index 00000000000..374584c3e1c
--- /dev/null
+++ b/developer/docs/help/guides/distribute/tutorial/step-6.md
@@ -0,0 +1,68 @@
+---
+title: Step 6: Compiling, testing and distributing a Package
+---
+
+In the Package Editor, click on the **Compile** tab.
+
+![](../../../images/tutorial_distribute_keyboard_compile.png)
+
+Click **Compile Package** to compile the
+package into a .kmp file. Compiling takes all the files listed for the
+package, compresses them (using a .ZIP-compatible format) and adds the
+package information, all into a single file. If any files are not
+available, an error will be listed in the Messages window.
+
+After compiling, you can test the package installation in Keyman
+Desktop, by clicking **Install Package**. You
+should test that all the keyboards and fonts install successfully, that
+the Readme and Welcome files are displayed during the install, and that
+the documentation is accessible to the end user. Make sure that the On
+Screen Keyboard installs with your keyboards, and that the shortcuts are
+correctly listed and working in the Start Menu.
+
+> ### Advice
+The distribution model for Keyman keyboards is changing. We now
+recommend distributing source for keyboards through the [Keyman Cloud Keyboard Repository](/developer/keyboards/). The instructions here
+remain, but the binary distribution model is deprecated.
+
+## Distributing a package on the Keyman Cloud Keyboard Repository
+
+Once you have tested the package to your satisfaction, it is time to
+distribute it. We recommend uploading your keyboard package to the
+[Keyman Cloud Keyboard Repository](/developer/keyboards/)
+
+## Distributing a package on your own website
+
+If you distribute a package on your own site, we have the following
+recommendations:
+
+1. Ensure the MIME type on the web server or folder for .KMX and .KMP
+ files is set up to application/octet-stream. Without this, .KMP
+ files may be recognised as .ZIP files -- this is not helpful to the
+ end user as it will be opened in the wrong application. If you can't
+ make this change, consider hosting the keyboard package on the
+ Tavultesoft website to make things easier for your end user.
+2. Avoid putting the .KMP file in an archive (e.g. .ZIP) or
+ self-expanding archive (.EXE) - this makes it harder for end users
+ to install. A .KMP file is already compressed (it is actually just a
+ ZIP archive file!) and you won't save much space by recompressing
+ it.
+3. Include a link to the Keyman download page:
+ `http://keyman.com/desktop/`
+
+## Distributing a package by email
+
+Attaching the KMP file directly to an email may be blocked for security
+reasons. As mentioned above, a KMP file is basically a ZIP file and
+mobile keyboard data is in JavaScript, this is a combination that looks
+suspicious to many email servers. You can upload it to a Google drive,
+and email a link for downloading the file
+
+You may need to do some exploring to get your mobile device to load the
+keyboard from the KMP file into Keyman. The device may not open the file
+directly, claiming it is an unrecognized type. If you look for other
+options to open the file, hopefully you can get it to Keyman. For
+example, in iOS, I had to tap on the three dots to the right, then
+choose "Open in" and then I could choose "Copy to Keyman." In Android, I
+had to download the file to my device, then open the KMP file in a file
+manager before I could install it in Keyman.
\ No newline at end of file
diff --git a/developer/docs/help/guides/index.md b/developer/docs/help/guides/index.md
new file mode 100644
index 00000000000..55604780ad2
--- /dev/null
+++ b/developer/docs/help/guides/index.md
@@ -0,0 +1,13 @@
+---
+title: Keyman Developer Guides
+---
+
+Need help using Keyman Developer? You'll find everything you need here, including product documentation, frequently asked questions and tutorials.
+
+- [Introduction to Keyman Developer](intro)
+- [Developing Keyman keyboards](develop/)
+- [Testing Keyman keyboards](test/)
+- [Distributing Keyman keyboards](distribute/)
+- [Developing Keyman lexical models (for predictive text and autocorrect)](lexical-models/)
+- [Distributing Keyman lexical models](lexical-models/distribute/)
+- [Keyman Developer command line tools guide](command-line)
\ No newline at end of file
diff --git a/developer/docs/help/guides/intro.md b/developer/docs/help/guides/intro.md
new file mode 100644
index 00000000000..fd72c898f91
--- /dev/null
+++ b/developer/docs/help/guides/intro.md
@@ -0,0 +1,43 @@
+---
+title: Introducing Keyman Developer
+---
+
+## Welcome to Keyman Developer
+
+Keyman Developer allows you to create your own keyboard layouts for use
+on the Keyman platform. Keyman is a multi-platform input method solution
+that runs on Windows, macOS, the web, mobile web, iOS and Android and
+Linux.
+
+There are two core applications included in Keyman Developer: the Keyman
+Developer IDE (formerly called TIKE), and the command line compiler kmc.
+
+[Watch a video](https://youtu.be/kwhgx_eX4Es) that highlights some of
+the Keyman Developer features.
+
+## Keyman Developer IDE
+
+The Keyman Developer IDE is a complete environment for designing,
+developing, testing, and packaging your keyboards for distribution. Some
+of the major features of Keyman Developer IDE are:
+
+- Keyboard Editor, for creating keyboards visually for all platforms
+- Touch Layout editor, for creating fluid touch layouts
+- Package Editor, for creating a package of keyboard, fonts and files for distribution (used on all platforms except web)
+- Lexical Model Editor, for creating predictive text models
+- Integrated compiler, to compile keyboards, packages and models
+- Fully interactive debugger for testing keyboards and models
+- Character Map, which supports viewing Unicode fonts under all Windows versions
+- On Screen Keyboard editor, for creating visual representations of keyboards
+- Projects, for managing keyboard and lexical model development projects large and small
+
+Source files (except images!) are normally written in UTF-8 format, so
+they can be opened with any text editor. However, the Keyman Developer
+IDE provides much tooling to simplify editing each of the types of
+files. The Keyman Developer IDE runs on Windows.
+
+## kmc, the command line compiler
+
+
+kmc, the command-line compiler, is a comprehensive tool that lets you
+compile keyboards, packages, models and projects from the command-line.
diff --git a/developer/docs/help/guides/lexical-models/advanced/index.md b/developer/docs/help/guides/lexical-models/advanced/index.md
new file mode 100644
index 00000000000..951df2fc48e
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/advanced/index.md
@@ -0,0 +1,19 @@
+---
+title: Advanced Lexical Models Topics
+---
+
+> For the following topics, **knowledge of TypeScript or JavaScript is
+required**.
+
+The Keyman lexical model engine is capable of much more sophisticated
+lexical models than the one described in the [tutorial](../tutorial). To
+make advanced models, you will need to modify the [model definition file](model-definition-file), also known as the [`.model.ts` file](../../../reference/file-types/model-ts).
+
+Once you understand how to change the model.ts file, you can attempt
+these customizations to an existing wordlist project:
+
+[Changing the default punctuation](punctuation)
+
+[Creating a custom search key function](search-term-to-key)
+
+[Customizing the word breaker](word-breaker)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/advanced/model-definition-file.md b/developer/docs/help/guides/lexical-models/advanced/model-definition-file.md
new file mode 100644
index 00000000000..97b207d8f32
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/advanced/model-definition-file.md
@@ -0,0 +1,102 @@
+---
+title: The model definition file
+---
+
+This is a small [TypeScript](https://www.typescriptlang.org/) source
+code file that tells us how to define our model.
+
+In the case of the **wordlist lexical models**, the model definition
+file indicates where to find the [TSV source files](../../../reference/file-types/tsv), as well as gives us the option to tell the compiler a little bit more about our language’s spelling system or *orthography*.
+
+## The model definition template
+
+**Keyman Developer** provides a default model definition similar to the
+following. If you want to create the file yourself, copy-paste the
+following template, and save it as `model.ts`. Place this file in the
+same folder as `wordlist.tsv`.
+
+```typescript
+/*
+ sencoten 1.0 generated from template.
+
+ This is a minimal lexical model source that uses a tab delimited wordlist.
+ See documentation online at https://help.keyman.com/developer/ for
+ additional parameters.
+*/
+
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ wordBreaker: {
+ use: 'default',
+ },
+ sources: ['wordlist.tsv'],
+};
+export default source;
+```
+
+Let's step through this file, line-by-line.
+
+On the first line, we're declaring the source code of a new lexical
+model.
+
+```typescript
+const source: LexicalModelSource = {
+```
+
+On the second line, we're saying the lexical model will use the
+`trie-1.0` format. The `trie` format creates a lexical model from one or
+more word lists; the `trie` structures the lexical model such that it
+can predict through thousands of words very quickly.
+
+```typescript
+ format: 'trie-1.0',
+```
+
+On lines 3–5, we're specifying the word breaking algorithm that we want
+to use. Keyman supplies a default algorithm that conforms to the rules
+expected for many languages.
+
+```typescript
+ wordBreaker: {
+ use: 'default',
+},
+```
+
+On the sixth line, we're telling the `trie` where to find our wordlist.
+
+```typescript
+ sources: ['wordlist.tsv'],
+```
+
+The seventh line marks the termination of the lexical model source code.
+If we specify any customizations, they **must** be declared above this
+line:
+
+```typescript
+};
+```
+
+The eighth line is necessary to allow external applications to read the
+lexical model source code.
+
+```typescript
+export default source;
+```
+
+## Customizing our lexical model
+
+The template, as described in the previous section, is a good starting
+point, and may be all you need for your language. However, most language
+require a few customizations. The `trie-1.0` wordlist model supports the
+following customizations:
+
+[Punctuation](punctuation)
+: How to define certain punctuation in your language.
+
+[Word breaker](word-breaker)
+: How to determine when words start and end in the writing system.
+
+[Search term to key](search-term-to-key)
+: How and when to ignore accents and letter case.
+
+To see all of the things possible in a model definition file, see the [`LexicalModelSource` interface](https://github.com/keymanapp/keyman/blob/stable-15.0/developer/js/source/lexical-model-compiler/lexical-model.ts#L95-L146).
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/advanced/punctuation.md b/developer/docs/help/guides/lexical-models/advanced/punctuation.md
new file mode 100644
index 00000000000..a665f08e4d8
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/advanced/punctuation.md
@@ -0,0 +1,73 @@
+---
+title: Punctuation
+---
+
+The lexical models use two different kinds of punctuation:
+
+- Quotation marks around the “keep” suggestion.
+- What kind of space to insert after words, if any.
+
+Both of these can be customized by adding the punctuation to [model definition file](./model-definition-file). Here is a full example of a model definition file using default punctuation:
+
+```typescript
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ sources: ['wordlist.tsv'],
+ // CUSTOMIZE THIS:
+ punctuation: {
+ quotesForKeepSuggestion: {
+ open: "“", close: "”"
+ },
+ insertAfterWord: " ",
+ },
+ // other customizations go here:
+};
+
+export default source;
+```
+
+## Customizing `quotesForKeepSuggestion`
+
+These are the quotation marks that surrond the “keep” suggestion when
+it's displayed in the suggestion bar. By defaut, the quotations used are
+“smart” quotation marks used in English typography. Namely, the **open
+quote** is `“` U+201C LEFT DOUBLE QUOTATION MARK, and the **close
+quote** is `”` U+201D RIGHT DOUBLE QUOTATION MARK.
+
+Let's customize this to use `«` and `»` for the open and close quote, respectively. To do this, change the part labeled
+`quotesForKeepSuggestion`:
+
+```typescript
+punctuation: {
+ quotesForKeepSuggestion: {
+ open: "«", close: "»"
+ },
+},
+```
+
+## Customizing `insertAfterWord`
+
+Many languages insert a space after a word. Some languages, like Thai or Khmer, do not use spaces. To suppress the space, you may set `insertAfterWord` to the empty string:
+
+```typescript
+punctuation: {
+ insertAfterWord: "",
+},
+```
+
+You can even use an alternate spacing character, if required by your language:
+
+```typescript
+punctuation: {
+ insertAfterWord: "\u200B", // U+200B ZERO WIDTH SPACE
+},
+```
+
+## See also
+
+[The TypeScript definition of
+`LexicalModelPunctuation`](https://github.com/keymanapp/keyman/blob/4211b468949860b8fb4a4707710472ab9e33c581/common/lexical-model-types/index.d.ts#L328-L371)
+
+------------------------------------------------------------------------
+
+[Return to “Advanced Lexical Model Topics”](./)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/advanced/search-term-to-key.md b/developer/docs/help/guides/lexical-models/advanced/search-term-to-key.md
new file mode 100644
index 00000000000..086a9ca01cf
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/advanced/search-term-to-key.md
@@ -0,0 +1,93 @@
+---
+title: Search term to key
+---
+
+To look up words quickly, the `trie` model creates a _search key_
+that takes the latest word (as determined by the
+[word breaker](word-breaker)) and converts it into an internal form. The
+purpose of this internal form is to make searching for a word work, as
+expected, regardless of things such as **accents**, **diacritics**,
+**letter case**, and minor **spelling variations**. The internal form is
+called the _key_. Typically, the key is always in
+lowercase, and lacks all accents and diacritics. For example, the key
+for “naïve" is `naive` and the key for “Canada” is `canada`.
+
+The form of the word that is stored is “regularized” through the use of a _key function_, which you can define in TypeScript code.
+
+> ### Note:
+This function runs both **on every word when the wordlist is compiled** and **on the input, whenever a suggestion is requested**. This way, whatever a user types is *matched* to something stored in the lexical model, without the user having to type things in a specific way.
+
+The key function takes a string which is the raw search term, and
+returns a new string, being the “regularized” key. As an example,
+consider the **default key function**; that is, the key function that is
+used if you do not specify one:
+
+```typescript
+searchTermToKey: function (term: string): string {
+ // Use this pattern to remove common diacritical marks (accents).
+ // See: https://www.compart.com/en/unicode/block/U+0300
+ const COMBINING_DIACRITICAL_MARKS = /[\u0300-\u036f]/g;
+
+ // Lowercase each letter in the string INDIVIDUALLY.
+ // Why individually? Some languages have context-sensitive lowercasing
+ // rules (e.g., Greek), which we would like to avoid.
+ // So we convert the string into an array of code points (Array.from(term)),
+ // convert each individual code point to lowercase (.map(c => c.toLowerCase())),
+ // and join the pieces back together again (.join(''))
+ let lowercasedTerm = Array.from(term).map(c => c.toLowerCase()).join('');
+
+ // Once it's lowercased, we convert it to NFKD normalization form
+ // This does many things, such as:
+ //
+ // - separating characters from their accents/diacritics
+ // e.g., "ï" -> "i" + "◌̈"
+ // - converting lookalike characters to a canonical ("regular") form
+ // e.g., ";" -> ";" (yes, those are two completely different characters!)
+ // - converting "compatible" characters to their canonical ("regular") form
+ // e.g., "𝔥𝔢𝔩𝔩𝔬" -> "hello"
+ let normalizedTerm = lowercasedTerm.normalize('NFKD');
+
+ // Now, using the pattern defined above, replace each accent and diacritic with the
+ // empty string. This effectively removes all accents and diacritics!
+ //
+ // e.g., "i" + "◌̈" -> "i"
+ let termWithoutDiacritics = normalizedTerm.replace(COMBINING_DIACRITICAL_MARKS, '');
+
+ // The resultant key is lowercased, and has no accents or diacritics.
+ return termWithoutDiacritics;
+},
+```
+
+This should be sufficient for most Latin-based writing systems. However,
+there are cases, such as with SENĆOŦEN, where some characters do not
+decompose into a base letter and a diacritic. In this case, it is
+necessary to write your own key function.
+
+## Use in your model definition file
+
+To use this in your model definition file, provide a function as the `searchTermToKey` property of the lexical model source:
+
+```typescript
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ sources: ['wordlist.tsv'],
+ searchTermToKey: function (wordform: string): string {
+ // Your searchTermToKey function goes here!
+ let key = wordform.toLowerCase();
+ return key;
+ },
+ // other customizations go here:
+};
+
+export default source;
+```
+
+## Suggested customizations
+
+- For all writing systems, **normalize into NFKD** or **NFKC** form using `wordform = wordform.normalize('NFKD')`.
+- For Latin-based scripts, **lowercase** the word, and **remove diacritics**.
+- For scripts that use the U+200C zero-width joiner (ZWJ) and/or the U+200D zero-width non-joiner (ZWNJ) (e.g., Brahmic scripts), **remove the ZWJ or ZWNJ** from the **end** of the input with `wordform = wordform.replace(/[\u200C\u200D]+$/`
+
+------------------------------------------------------------------------
+
+[Return to “Advanced Lexical Model Topics”](./)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/advanced/unicode-breaker-extension.md b/developer/docs/help/guides/lexical-models/advanced/unicode-breaker-extension.md
new file mode 100644
index 00000000000..a9e4d3776cc
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/advanced/unicode-breaker-extension.md
@@ -0,0 +1,539 @@
+---
+title: Customization and extension of the Unicode word breaker
+---
+
+> ### Note:
+While this offers a great amount of flexibility and customization that may assist predictions for your language, note that this approach may get quite technical!
+
+As mentioned on the [main word breaker page](./word-breaker),
+our currently-supported lexical models need to know what a word
+is in running text. After all, it is quite difficult to look up a word in a
+dictionary when unclear about what the start and end of that word even is.
+
+In languages using the Latin script—like, English, French,
+and SENĆOŦEN—finding words is easy. Words are separated by spaces or
+punctuation. The actual rules for where to find words can get quite tricky to
+describe, but Keyman implements the [Unicode Standard Annex #29 §4.1 Default Word Boundary Specification](https://unicode.org/reports/tr29/#Word_Boundaries) which works well for most languages.
+
+This guide is about techniques that may be used to customize and extend
+the behaviors of that specification to be better tailored to your language.
+This is done by using similar patterns and structures to the rules found in
+the specification itself.
+
+There are three ways - all of them optional - to extend and customize
+the word-breaking rules themselves:
+
+* If you need to prevent splits in very specific scenarios and/or add splits in other specific scenarios, you may specify [context-based rules](#rules) to obtain the desired behavior.
+* If certain characters are not handled appropriately for their role in
+ your language, you may [map characters](#map) to different
+ word-breaking character classes - including custom ones. This will
+ override the default property they are assigned by the default
+ implementation, with the new property applying for all word-breaking
+ rules.
+* If the default word-breaking classes from the specification are
+ too general for certain aspects of your language, it is possible to
+ [define custom character classes](#define) for use in custom
+ rules.
+
+## A first example
+
+This example was designed to address the needs of a minority language in the country
+of Cambodia. The majority language does not use spaces for wordbreaking, while the
+minority language in question does use them. In addition, hyphens sometimes occur
+within words.
+
+The word breaker function can be specified in the
+[model definition file](./model-definition-file) as follows:
+
+```typescript
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ sources: ['wordlist.tsv'],
+ // CUSTOMIZE THIS:
+ wordBreaker: (text) => {
+ let customization = {
+
+ /*** Definition of extra word-breaking rules ***/
+ rules: [{
+ match: (context) => {
+ if(context.propertyMatch(null, ["ALetter"], ["Hyphen"], ["ALetter"])) {
+ return true;
+ } else if(context.propertyMatch(["ALetter"], ["Hyphen"], ["ALetter"], null)) {
+ return true;
+ } else if(context.propertyMatch(null, ["ALetter"], ["Hyphen"], ["eot"])) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+ breakIfMatch: false
+ }],
+
+ /*** Character class overrides for specific characters ***/
+ propertyMapping: (char) => {
+ let hyphens = ['\u002d', '\u2010', '\u058a', '\u30a0'];
+ // treats Khmer consonants & independent vowels in the same manner
+ // as the basic latin-script based alphabet
+ if(char >= '\u1780' && char <= '\u17b3') {
+ return "ALetter";
+ } else if(hyphens.includes(char)) {
+ return "Hyphen";
+ } else {
+ // The other Khmer characters already have useful word-breaking
+ // property assignments.
+ return null;
+ }
+ },
+
+ /*** Declares any new, custom character classes to be recognized by the word-breaker ***/
+ customProperties: ["Hyphen"]
+ };
+
+ /*** Connects all the pieces together for actual use ***/
+ return wordBreakers['default'](text, customization);
+ },
+ // ...
+};
+
+export default source;
+```
+
+This example's customization is designed to accomplish two goals:
+
+1. Unicode's wordbreaker does not map base Khmer consonants to any of the
+ [relevant wordbreaking character properties](https://unicode.org/reports/tr29/#Table_Word_Break_Property_Values), causing it to be treated as `"Other"`.
+ The minority language in question instead wishes for Latin-script-like word-breaking,
+ so mapping the consonants to the same property as Latin-script consonants allows
+ them to be treated similarly - in the manner they expect.
+
+2. By default, the wordbreaker will automatically insert word boundaries before
+ and after a hyphen. To allow mid-word hyphens, we need to remap them to
+ a different character property.
+
+ While the specification itself mentions that we could
+ just map hyphens to `"MidLetter"`, this example opts to define
+ a custom-tailored property that ensures only hyphens are affected in order to add
+ a special, end-of-context rule that may be useful when typing - the rule above
+ referencing `"eot"`.
+
+More on the wordbreaking character properties will be covered later.
+
+## Custom word-breaking rules
+
+When defining additional rules for use in word-breaking, it is advisable to reference
+the [rules of the Unicode Standard Annex #29 §4.1 Default Word Boundary Specification](https://unicode.org/reports/tr29/#Word_Boundary_Rules).
+
+Rules WB1 through WB4 of the specification will always apply first, before any
+custom rules. Custom rules will then be applied in order of their definition within
+the model, with them all being applied before the specification's rules labeled WB5
+and onward.
+
+Each rule should be of the following form:
+
+```typescript
+{
+ // A function that returns 'true' whenever the rule should apply
+ match: (context) => {
+ // ...
+ },
+ // Whether to prevent (false) or to enforce (true) a boundary when the rule applies.
+ breakIfMatch: false
+}
+```
+
+Toward this end, the `context` object received by match provides a function called
+`propertyMatch` in order to define rules like those of the wordbreaker specification.
+Let's take WB6 - "do not break letters across certain punctuation" - as an example.
+
+As written in the spec, WB6 reads **AHLetter x (MidLetter | MidNumLetQ) AHLetter**.
+
+This is simply a series of characters, up to two characters before and after a potential break point. To break that down:
+- (implicit) **Any** -- accept any character in this position
+- **AHLetter**: `ALetter` or `Hebrew_Letter`
+- **x** - "do not break"
+- (either) **MidLetter** or **MidNumLetQ**: one of `MidLetter`, `MidNumLet`, or `Single_Quote`.
+- **AHLetter**: `ALetter` or `Hebrew_Letter`
+
+The expansions `MidNumLetQ` and `AHLetter` are defined at https://unicode.org/reports/tr29/#WB_Rule_Macros.
+
+If written as a custom rule, rule WB6 takes the following form. Note the use of the function `context.propertyMatch`,
+which takes 4 parameters - two to match characters before and two to match characters after a potential boundary:
+
+```typescript
+// Rule WB6 from the Unicode spec, as a custom rule:
+{
+ match: (context) => {
+ return context.propertyMatch(null, // no requirements set, so "Any" character may match
+ ["ALetter", "Hebrew_Letter"],
+ // x
+ ["MidLetter", "MidNumLet", "Single_Quote"],
+ ["ALetter", "Hebrew_Letter"]);
+ },
+ breakIfMatch: false // do not break
+}
+```
+
+Note that an empty array `[]` in any slot is not treated the same as `null` -
+use of an empty `[]` will prevent the rule from matching.
+
+Almost all of the specification's rules are of this form. Again, up to two characters
+before a potential boundary may be considered alongside up to two characters after
+the potential boundary.
+
+### Word-breaking property names
+The names used in each array must be defined in one of the following places:
+* https://unicode.org/reports/tr29/#Table_Word_Break_Property_Values
+* `customProperties` - your [declaration of any custom property types](#define)
+* One of the special property types `"Other"`, `"sot"`, or `"eot"`:
+ * `Other`: a character without an associated word-breaking property value
+ * `sot`: "start of text" - a marker indicating the beginning of the string being word-broken
+ * `eot`: "end of text" - a marker indicating the end of the string being word-broken
+
+All rules use case-insensitive matching, so capitalization differences will not affect operation.
+
+### Rule-matching examples
+
+#### A successful rule application
+As an example, when determining whether or not to break the English word `don't` when
+applying WB6 as written above, this is what happens near the apostrophe:
+
+```typescript
+{
+ match: (context) => {
+ return context.propertyMatch(null, /* match any character */ // "o" - ALetter
+ ["ALetter", "Hebrew_Letter"], // "n" - ALetter
+ // x
+ ["MidLetter", "MidNumLet", "Single_Quote"], // "'" - Single_Quote
+ ["ALetter", "Hebrew_Letter"]); // "t" - ALetter
+ },
+ breakIfMatch: false // do not wordbreak at any position where the rule matches.
+}
+```
+
+The rule applies at the position between the `n` and the `'` of `don't`, telling the
+word-breaker not to word-break if the rule matches at that location.
+
+#### An unsuccessful rule match
+
+Given the input `n't|` (where `|` is the caret), we can see that the rule will not match:
+```typescript
+{
+ match: (context) => {
+ return context.propertyMatch(null, /* automatic match */ // "n" - ALetter
+ ["ALetter", "Hebrew_Letter"], // "'" - Single_Quote
+ // x
+ ["MidLetter", "MidNumLet", "Single_Quote"], // "t" - Single_Quote
+ ["ALetter", "Hebrew_Letter"]); // - eot
+ },
+ breakIfMatch: false // do not break
+}
+```
+
+The same rule does not apply between the `'` and the `t`, so it does not apply at this position in
+the text. This is why the wordbreaking spec includes rule WB7 in addition to WB6 - some scenarios
+require multiple context property-matching attempts.
+
+You may define any number of these rule objects in any order for use within the wordbreaker within
+the `rules` array.
+
+> **Important note**: When no other rule successfully matches a potential word-boundary position, the spec's rule WB999 applies as a catch-all and will enforce a split. Preventing a word-break always requires a successfully-matching rule, though this is covered for most cases by the Unicode specification's ruleset.
+
+Defining custom rules is a powerful tool, but it is detailed work and may be somewhat tedious
+to get right. Feel free to [ask for help at our Community Site](https://community.software.sil.org/c/keyman/19) for assistance.
+
+## Character property remapping
+
+Many writing systems in the world are shared by multiple languages, using most of the same
+characters in common. However, sometimes there may be notable differences in how specific
+languages wish to treat certain characters. In order to address these cases, we allow
+overriding the "standard" word-breaking property that the Unicode specification gives the
+character with one set by the lexical model.
+
+### Default character properties
+
+For reference, [this text file](https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/WordBreakProperty.txt)
+provides the standard word-breaking properties for all characters. This is one of many
+files Unicode provides publicly here: https://www.unicode.org/reports/tr41/#Props0.
+
+That text file contains many lines of the following form:
+
+```
+0041..005A ; ALetter # L& [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z
+```
+
+This says that `0041` through `005A` - or rather, `\u0041` (the code for 'A') through
+`\u005A` (the code for 'Z') - are assigned word-breaking property `ALetter`. That
+range covers 26 (`[26]`) characters. (For our purposes here, the `L&` part is
+irrelevant.)
+
+As noted at the top of the file:
+
+```
+# All code points not explicitly listed for Word_Break
+# have the value Other (XX).
+
+# @missing: 0000..10FFFF; Other
+```
+
+### Redefining character properties
+
+Of note from [our first example](#example):
+
+```typescript
+/*** Character class overrides for specific characters ***/
+propertyMapping: (char) => {
+ // ...
+ // treats Khmer consonants & independent vowels in the same manner
+ // as the basic latin-script based alphabet
+ if(char >= '\u1780' && char <= '\u17b3') {
+ return "ALetter";
+ } // ...
+},
+```
+
+If you search the [property definition text file](https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/WordBreakProperty.txt)
+for `1780` or `17b3`, you will find neither. These correspond to many letters
+from the Khmer character set - notably, the 'base' characters used in Khmer's
+grapheme clusters. The other Khmer characters tend to attach at various positions
+around these base characters. The majority language for the script - Khmer -
+does not follow conventional word-breaking rules; most notably, they do not add
+whitespace between each word. (There are other strategies that get utilized for
+such scripts.)
+
+As breaks sometimes occur between the base characters while other times do not,
+properties for these base characters were not explicitly defined and are thus
+treated as class `Other`.
+- As other Khmer characters tend to attach around the base characters, they do
+ have specified word-breaking properties - they `Extend` the grapheme cluster.
+- Searching `17b4` in [the text file mentioned above](https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/WordBreakProperty.txt) will show the closest results to the characters under discussion.
+
+However, there are minority languages that prefer to use whitespaces between words,
+meaning that there should never be wordbreaks applied directly between neighboring
+characters for their words. In such cases, we can map them to a pre-existing property
+with the desired behavior - `ALetter`.
+
+```typescript
+if(char >= '\u1780' && char <= '\u17b3') {
+ return "ALetter"; // Maps Khmer-script base characters to ALetter
+ // to allow Latin-script-like word-breaking.
+}
+```
+
+For another example of character property remapping, consider the use of hyphens with
+words (and/or names) in some languages. Default word-breaking behavior will split
+hyphenated words and names apart, but by changing the property of hyphens, it is
+possible to disable this behavior.
+
+Noting [rule WB6](#WB6) and WB7, the `MidLetter` class is designed to prevent
+word-breaks from occurring when its characters lie directly between letters -
+hence the property name. Assigning hyphens to this class can provide the
+desired behavior.
+
+```typescript
+let hyphens = ['\u002d', '\u2010', '\u058a', '\u30a0'];
+// ...
+if(hyphens.includes(char)) {
+ return "MidLetter";
+} // ...
+```
+
+## Defining and using new word-breaking properties
+
+There may be some cases in which none of the default character word-breaking
+properties provide the exact behavior that you're wanting, or perhaps you
+want only specific characters from that class to match custom rules. For such
+cases, wordbreaker customization also allows definition of new word-breaking
+properties.
+
+For one example, note how word-breaking operations affect predictions when
+typing new words:
+
+- `can'`, with the intent to type `can't`
+- `full-`, with the intent to type `full-scale`
+
+For the first example above, while `'` (property `Single_Quote`) is included within
+WB6 and WB7, those rules only apply _between letters_. If there is no letter
+on the right-hand side, `can'` will be interpreted as `can` + `'` by the
+word-breaking algorithm. Similarly, even when remapping `-` to the `MidLetter`
+property, `full-` will be remapped to `full` + `-` before additional text
+is typed.
+
+Of course, this problem does alleviate itself once another `ALetter`-property
+letter is typed, but suppose we wanted a rule to prevent word-breaking for
+the second example above. (After all, `can'` could be the end of a quoted
+phrase in English - `'sure you can'` - in which case we might want the split
+to occur.)
+
+Revisiting [an earlier example](#example) and simplifying a little bit:
+
+```typescript
+/*** Definition of extra word-breaking rules ***/
+{
+ rules: [{
+ match: (context) => {
+ if(context.propertyMatch(null, ["ALetter"], ["Hyphen"], ["eot"])) {
+ return true;
+ } else {
+ /* ... */
+ } else {
+ return false;
+ }
+ },
+ breakIfMatch: false
+ }],
+
+ /*** Character class overrides for specific characters ***/
+ propertyMapping: (char) => {
+ let hyphens = ['\u002d', '\u2010', '\u058a', '\u30a0'];
+ if(hyphens.includes(char)) {
+ return "Hyphen";
+ } else {
+ // Use the default properties for anything else.
+ return null;
+ }
+ },
+
+ /*** Declares any new, custom character classes to be recognized by the word-breaker ***/
+ customProperties: ["Hyphen"]
+}
+```
+
+Let's walk through what this simplification is trying to achieve:
+
+1. Hyphens are mapped to their own distinct word-breaking property.
+2. The custom rule prevents wordbreaking between a letter and a hyphen at the end of text.
+ - It does not include any of the `MidLetter` characters.
+
+Matching the rule against the end of text suggests that what follows the `Hyphen` character
+is the point of text insertion. For this example, assuming that a user has just typed
+`full-`, there will be no word-break on the hyphen until either more input is received or
+the user changes the site of text entry.
+
+**Important note**: You must declare any custom properties within the `customProperties` array.
+If any are missing, the missing custom properties will fail to match against any word-breaking rule.
+Make sure you don't misspell it anywhere in your customization code!
+
+### Default rules + custom properties
+
+There is one notable issue with this example, though - whenever you remap a character to
+a new property, it is no longer considered to have its old property, and so it will no
+longer match rules based on its default property. This is why the original example
+included a couple of extra rules:
+
+```typescript
+{
+ match: (context) => {
+ // Extend WB6 - allow "Hyphen" in the same place as "MidLetter"
+ if(context.propertyMatch(null, ["ALetter"], ["Hyphen"], ["ALetter"])) {
+ return true;
+ // Extend WB7 - allow "Hyphen" in the same place as "MidLetter"
+ } else if(context.propertyMatch(["ALetter"], ["Hyphen"], ["ALetter"], null)) {
+ return true;
+ // The same rule from above.
+ } else if(context.propertyMatch(null, ["ALetter"], ["Hyphen"], ["eot"])) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+ breakIfMatch: false
+}
+```
+
+By replicating [WB6](#WB6) and WB7's structure and allowing `Hyphen` to match in the same
+position as `MidLetter` in the original rules, we can prevent word-breaking splits
+after additional text has been typed after a `Hyphen`-property character. This does not
+_replace_ the behavior of WB6 and WB7 - it merely _extends_ it to include the new property.
+
+### A more complex case
+
+A meatier example may be found as [the specification's hypothetical rule WB5a](https://unicode.org/reports/tr29/#WB999):
+
+> "Break between apostrophe and vowels (French, Italian)"
+>
+> WB5a: **Apostrophe ÷ Vowels**.
+
+The idea of this rule is to allow words such as the French `l'objectif` to be split
+into the article - `l'` and its following word - `objectif` while preserving other
+cases that should still be treated as single words, such as `aujourd'hui`.
+
+To simplify the code needed for customization here somewhat, we will use `Single_Quote`
+in place of `Apostrophe`, as well as a few extra simplifications. The new `Vowels`
+property offers enough complexity as it is.
+
+```typescript
+let customization = {
+ rules: [
+ // WB5 extension - ensure `AVowel` is handled like `ALetter`.
+ {
+ match: (context) => {
+ if(context.propertyMatch(null, ["ALetter", "AVowel"], ["ALetter", "AVowel"], null)) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+ breakIfMatch: false
+ },
+ // Our main goal WB5a
+ {
+ match: (context) => {
+ if(context.propertyMatch(null, ["Single_Quote"], ["AVowel"], null)) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+ breakIfMatch: true
+ },
+ // WB6, 7 extension - ensure `AVowel` is handled like `ALetter`
+ {
+ match: (context) => {
+ if(context.propertyMatch(null,
+ ["ALetter", "AVowel"],
+ ["MidLetter", "MidNumLet", "Single_Quote"],
+ ["ALetter", "AVowel"])) {
+ return true;
+ } else if(context.propertyMatch(["ALetter", "AVowel"],
+ ["MidLetter", "MidNumLet", "Single_Quote"],
+ ["ALetter", "AVowel"],
+ null)) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+ breakIfMatch: false
+ }
+ // Similar extensions to WB9, 10, 13a, and 13b would also be needed for robustness.
+ // Note: we have left "Hebrew_Letter" out of the WB5, 6, and 7 rewrites to help
+ // simplify this example.
+ ],
+ propertyMapping: (char) => {
+ const vowels = ['a', 'e', 'i', 'o', 'u'];
+ // French and Italian allow accented vowels; this will strip off the accent and
+ // leave us with the base vowel.
+ const baseChar = char.normalize('NFD').charAt(0);
+ if(vowels.includes(baseChar)) {
+ return "AVowel";
+ }
+
+ return null;
+ },
+ customProperties: ["AVowel"]
+}
+```
+
+Note that we have left out some of the rule extensions that would help cover certain
+less-frequently encountered cases. These may matter to some parts of a
+language community, especially for models targeting a majority language.
+Computer programmers in particular tend to care about the un-extended
+rules (WB9, 10, 13a, and 13b) more than most.
+
+Remember, remapping characters to a new word-breaking property prevents any default
+rule from handling them unless you add custom rules to re-include them as their
+new property.
+
+------
+
+[Return to “Advanced Lexical Model Topics”](./)
diff --git a/developer/docs/help/guides/lexical-models/advanced/word-breaker.md b/developer/docs/help/guides/lexical-models/advanced/word-breaker.md
new file mode 100644
index 00000000000..f8f321327ac
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/advanced/word-breaker.md
@@ -0,0 +1,218 @@
+---
+title: Word breaker
+---
+
+The `trie` family of lexical models needs to know what a word is in
+running text. In languages using the Latin script—like, English, French,
+and SENĆOŦEN—finding words is easy. Words are separated by spaces or
+punctuation. The actual rules for where to find words can get quite
+tricky to describe, but Keyman implements the [Unicode Standard Annex #29 §4.1 Default Word Boundary Specification](https://unicode.org/reports/tr29/#Word_Boundaries)
+which works well for most languages.
+
+However, in languages written in other scripts — especially East Asian
+scripts like Chinese, Japanese, Khmer, Lao, and Thai — there are no obvious break in between words. For these languages, there must be special rules for determining when words start and stop. This is what a _word breaker function_ is responsible for. It is a little bit of code that looks at some text to determine where the words are.
+
+You can customize the word breaker in three ways:
+- If your language uses its writing system in an unconventional way (e.g., use spaces to separate words in Thai, Lao, Burmese, or Khmer), you can [override the script's default behaviour](#overrides)
+- If the default word breaker creates **too many splits**, you can [choose which strings join words together](#join).
+- If the default word breaker creates **not enough splits**, you must [create your own word breaker function](#custom).
+- Alternatively, you may choose to [customize and extend the wordbreaker's behavior](./unicode-breaker-extension) by adding extra rules and changing how it treats specific characters.
+
+## Overriding script defaults
+
+The default word breaker makes assumptions about how each
+_script_ (alphabet, syllabary, or writing system)
+works. You can override the defaults by specifying the
+`overrideScriptDefaults` option.
+
+There is currently only one override:
+
+`'break-words-at-spaces'`
+: Only breaks words at spaces for scripts that otherwise do not use spaces in between words.
+
+### Break words at spaces
+
+This applies only to languages that borrow the **Burmese**, **Khmer**,
+**Lao**, or **Thai** scripts. The majority languages for these scripts
+do *not* use spaces in between words; hence, the default word breaker
+will produce undesired results when breaking words in these scripts.
+However, if your language is written in one of these scripts and *does*
+use spaces in between words, then you can set
+`overrideScriptDefaults: 'break-words-at-spaces',` to ensure word breaks
+do not occur in the middle of words, but instead, at spaces.
+
+Your model definition file should look like this:
+
+```typescript
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ sources: ['wordlist.tsv'],
+ wordBreaker: {
+ use: 'default', // we want to use the default word breaker, BUT!
+ // Override the default for Burmese, Khmer, Lao, or Thai:
+ overrideScriptDefaults: 'break-words-at-spaces',
+ }
+};
+
+export default source;
+```
+
+## Customize joining rules
+
+The default word breaker is very liberal in what it considers is a word.
+
+For instance, the default word breaker will split words at hyphens.
+Consider the following Plains Cree example; this is a single word:
+
+: amiskwaciy-wâskahikan
+
+However, the default word breaker will produce three words: `amiskwaciy`, `-`, and `wâskahikan`.
+
+To **join words at hyphens and any other punctuation**, provide the
+`joinWordsAt` option in the [model definition file](./model-definition-file):
+
+```typescript
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ sources: ['wordlist.tsv'],
+ wordBreaker: {
+ use: 'default', // we want to use the default word breaker, BUT!
+ // CUSTOMIZE THIS:
+ joinWordsAt: ['-'], // join words that contain hyphens
+ }
+};
+
+export default source;
+```
+
+You can specify one or more strings to join words at:
+
+```typescript
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ sources: ['wordlist.tsv'],
+ wordBreaker: {
+ use: 'default',
+ // CUSTOMIZE THIS:
+ joinWordsAt: ['-', ':', '@'], // join words at hyphens, colons, at-signs
+ }
+};
+
+export default source;
+```
+
+## Writing a custom word breaker function
+
+> **Note**:
+If your language uses spaces to denote word breaks, the
+default word breaker is probably sufficient. Only customize this if you
+know the default word breaker really does not work for your language!
+
+The word breaker function can be specified in the [model definition file](./model-definition-file) as follows:
+
+```typescript
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ sources: ['wordlist.tsv'],
+ // CUSTOMIZE THIS:
+ wordBreaker: {
+ use: function(text: string): Span[] {
+ // Return zero or more **spans** of text:
+ return [];
+ },
+ },
+ // other customizations go here:
+};
+
+export default source;
+```
+
+The function must return zero or more `Span` objects. The spans,
+representing an indivisible span of text, must be in ascending order of
+their start point, and they must be non-overlapping.
+
+### A `Span` object
+
+A _span_ is an indivisible piece of a sentence.
+This is typically a word, but it can also be a series of spaces, an
+emoji, or a series of punctuation characters. **A span that looks like a word is treated like a word in the `trie-1.0` model**.
+
+A `span` has the following properties:
+
+```typescript
+{
+ start: number;
+ end: number;
+ length: number;
+ text: string;
+}
+```
+
+The `start` and `end` properties are indices into the original string at
+which the span begins, and the index at which the *next* span begins.
+
+`length` is `end - start`.
+
+`text` is the actual text of the string contained within the span.
+
+## Example for English
+
+Here is a full example of word breaker function that returns an array of
+spans in an ASCII (English) string.
+
+> **Note**
+This is just an example—please
+use the default word breaker for English text!
+
+```typescript
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ sources: ['wordlist.tsv'],
+ // EXAMPLE BEGINS HERE:
+ wordBreaker: function(text: string): Span[] {
+ // A span derived from a JavaScript RegExp match array:
+ class RegExpDerivedSpan implements Span {
+ readonly text: string;
+ readonly start: number;
+
+ constructor(text: string, start: number) {
+ this.text = text;
+ this.start = start;
+ }
+
+ get length(): number {
+ return this.text.length;
+ }
+
+ get end(): number {
+ return this.start + this.text.length;
+ }
+ }
+
+ let matchWord = /[A-Za-z0-9']+/g;
+ let words: Span[] = [];
+ let match: RegExpExecArray;
+ while ((match = matchWord.exec(phrase)) !== null) {
+ words.push(new RegExpDerivedSpan(match[0], match.index));
+ }
+
+ return words;
+ },
+ // other customizations go here:
+};
+
+export default source;
+```
+
+## See also
+
+- [The TypeScript definition of `WordBreakingFunction` and
+`Span`](https://github.com/keymanapp/keyman/blob/4211b468949860b8fb4a4707710472ab9e33c581/common/lexical-model-types/index.d.ts#L286-L323)
+
+- [Extension and customization of the Unicode word-breaker](./unicode-breaker-extension)
+
+- [The Unicode Standard Annex \#29 §4.1 Default Word Boundary Specification](https://unicode.org/reports/tr29/#Word_Boundaries)
+
+------------------------------------------------------------------------
+
+[Return to “Advanced Lexical Model Topics”](./)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/distribute/index.md b/developer/docs/help/guides/lexical-models/distribute/index.md
new file mode 100644
index 00000000000..42f3741af17
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/distribute/index.md
@@ -0,0 +1,6 @@
+---
+title: Distributing lexical models
+---
+
+- [Lexical model package development tutorial](tutorial/) (Keyman for mobile apps)
+- [Distribute lexical models to Keyman applications](packages)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/distribute/packages.md b/developer/docs/help/guides/lexical-models/distribute/packages.md
new file mode 100644
index 00000000000..92e61650308
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/distribute/packages.md
@@ -0,0 +1,70 @@
+---
+title: Distribute lexical models to Keyman Applications
+---
+
+## Overview
+
+Lexical model package files contain one lexical model, along with readme
+files, and any other files you wish to include. You must create a
+package file to bundle your lexical model and help documentation into a
+simple, single file that is easy for an end-user to install.
+
+The package file is a ZIP compatible archive.
+
+> ### Tip
+You can distribute keyboards and lexical models in package files, but
+you can't include both in the same package file.
+
+#### Keyman for Android and Keyman for iPhone and iPad
+
+Keyman mobile applications can install the same lexical model package
+files.
+
+## Package file contents
+
+A package can have a variety of different files contained within. The
+following files and file types are recognized by the package installer:
+
+\*.model.js
+: Lexical model file. When Keyman mobile applications install a
+ lexical model package, the included model will be installed and
+ associated with the specified languages.
+
+welcome.htm
+: Introductory help for the lexical model, HTML format. This will
+ normally be displayed when the package is installed by the user, and
+ is also the entry point for help when accessed via Keyman's help
+ system or Keyman Configuration.
+
+## Step 1) Share the lexical model package file
+
+Once the lexical model package .kmp file is created, you can share them
+via external storage devices (USB drive, SD card, etc). If that is not
+an option, you can upload the .kmp file to a public facing website. For
+this example, the lexical model package for SENĆOŦEN is being uploaded:
+
+1. nrc.str.sencoten.model.kmp (the lexical model package .kmp file)
+
+## Step 2) Create a link to the KMP file
+
+Once all the files have been uploaded, you will need to provide a link
+to the lexical model package .kmp file for your device to download and
+install. This can either be a link on a web page, or a link in an email.
+In this tutorial, a very simple .html web page with a link to the
+nrc.str.sencoten.kmp file is created:
+
+```html
+
+
+
+
+ Sencoten Lexical Model Package
+
+
+```
+
+The link must be in the format `http://` or `https://`
+
+Upload the web page to your public facing website. Once done, you can
+install the lexical model package onto your mobile devices by
+downloading the .kmp from your device's internet browser.
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/distribute/tutorial/index.md b/developer/docs/help/guides/lexical-models/distribute/tutorial/index.md
new file mode 100644
index 00000000000..fe4e0d039ec
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/distribute/tutorial/index.md
@@ -0,0 +1,32 @@
+---
+title: Lexical Model Package Tutorial
+---
+
+[Step 1: What do we include?](step-1)
+
+[Step 2: Editing .htm files for the Package](step-2)
+
+[Step 3: Checking a package and adding files](step-3)
+
+[Step 4: Filling in package details](step-4)
+
+[Step 5: Compiling, testing and distributing a Package](step-5)
+
+## Overview
+
+Welcome! In this tutorial, you will learn how to create a lexical model
+[package](../../../../reference/file-types/kmp). A package is a
+collection of files, compressed into a single file, just like a ZIP
+file. It is designed to make installation of a lexical model
+straightforward for the end user. When done, Keyman for mobile apps can
+download and install lexical models through the package.
+
+We will be creating a package for the SENĆOŦEN lexical model that was
+made in [Developing a lexical model from a word list](../../tutorial/).
+
+### Let's begin
+
+Let's get started! Move on to the next topic to begin the first step,
+choosing what to include in the package.
+
+[Step 1: What do we include?](step-1)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/distribute/tutorial/step-1.md b/developer/docs/help/guides/lexical-models/distribute/tutorial/step-1.md
new file mode 100644
index 00000000000..d27970b9e43
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/distribute/tutorial/step-1.md
@@ -0,0 +1,15 @@
+---
+title: Step 1: What do we include?
+---
+
+What is the purpose of a lexical model package? To make installation of
+a lexical model as straightforward as possible for the end user. We need
+to keep this goal in mind as we work on all the aspects of a package.
+
+A great package will:
+
+1. Include one lexical model to install.
+2. Include a 'readme.htm' file which describes why the user would install this package.
+3. Include a 'welcome.htm' file which is displayed before/after install.
+
+[Step 2: Editing .htm files for the package](step-2)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/distribute/tutorial/step-2.md b/developer/docs/help/guides/lexical-models/distribute/tutorial/step-2.md
new file mode 100644
index 00000000000..10a560fd194
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/distribute/tutorial/step-2.md
@@ -0,0 +1,72 @@
+---
+title: Step 2: Editing .htm files for the package
+---
+
+When Keyman Developer created your lexical model project, it will have
+created some of these files to go in the package. You will still need to
+edit some of these templated files for the package.
+
+readme.htm
+
+: A short description of the package, its use restrictions, and what
+ it includes. Try to keep the readme under 10 lines long. The readme
+ should be an html file for optimal formatting.
+
+ The intention of readme.htm is to describe why a user would want to
+ install the lexical model.
+
+ Create the HTML file in any HTML editor.
+
+ > ### Tip
+ If using Microsoft Word, choose HTML (clean) when saving to create a
+ smaller file.
+
+ Note, if your HTML editor puts images into a subfolder, you will
+ need to edit the HTML source so that all files are in the same
+ folder -- the package builder will not maintain subfolders. You can
+ easily edit the HTML source in Keyman Developer.
+
+ Also, if your welcome or readme files use embedded external images,
+ stylesheets, javascript or other files, you will need to add these
+ files to your package as well.
+
+welcome.htm
+
+: When including an introductory help file in your package, you must
+ keep the name of the file "welcome.htm". This file will be displayed
+ before the lexical model is installed. Make sure that you design
+ your HTML file so that it can be viewed on a mobile device - avoid
+ extra wide tables or wide fixed width elements.
+
+ After package installation, the welcome.htm will also be accessible
+ from the lexical model info pages.
+
+ The intention of welcome.htm is to provide instructions on getting
+ started with your lexical model.
+
+ > ### Tip
+ Useful information to include in welcome.htm is:
+ - names of languages associated with the lexical model.
+ - name of the lexical model in the package.
+ - links to additional help on your website or more extensive
+ documentation files in the package.
+ - a link to an official distribution site for your package (even
+ if it is the Keyman website) - so that users know where to find
+ the latest version of your package.
+
+
+
+ > ### Tip
+ If you want links to your website to open in the user's preferred
+ browser, preface the href link with `link:`, e.g.
+ `website `
+ The `link:` sceheme will open the referred file in the default
+ application - that is, a web browser for URLs and links, Notepad for
+ .txt files, Adobe Reader for PDFs. You can use `link:` to open any
+ of the files in the package, e.g. `link:docs.pdf` will open the file
+ docs.pdf in Adobe Reader or the default PDF reader on the system.
+
+ If you create documents in other formats, for example PDF or
+ printable documentation, you should link to that in the welcome.htm.
+
+[Step 3: Checking a package and adding files](step-3)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/distribute/tutorial/step-3.md b/developer/docs/help/guides/lexical-models/distribute/tutorial/step-3.md
new file mode 100644
index 00000000000..9d1ea260002
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/distribute/tutorial/step-3.md
@@ -0,0 +1,51 @@
+---
+title: Step 3: Checking a package and adding files
+---
+
+We are finally ready to open up the Package Editor and check the package
+contents. In the Project Window in Keyman Developer, click the
+**Packaging** tab.
+
+> ### Tip
+This tutorial makes a package named `nrc.str.sencoten.model.kps`, but
+you should substitute the name with your own model.
+
+A lexical model package source file will have the extension .model.kps,
+and will be compiled in a file with extension .kmp.
+
+## Files
+
+In the Package Editor, click on the **Files**
+tab.
+
+![Lexical Model Package Files](../../../../images/lm/tutorial_distribute_model_3_files.png)
+
+Keyman Developer already included your lexical model, welcome.htm, and
+readme.htm files in your package. In the Files tab, click
+**Add** to add all the additional files we
+discussed in the previous step to the package. For example, if your
+welcome or readme files use embedded files, include them in your
+package. You can add multiple files at once, and from multiple folders.
+When the package is compiled, all the files will be placed in the same
+folder within the package.
+
+While keyboards can also be distributed in packages, do not include them
+in a lexical model package.
+
+## Lexical Models
+
+In the Package Editor, click on the **Lexical Models** tab.
+
+![Lexical Model Info](../../../../images/lm/tutorial_distribute_model_3.png)
+
+Language Tag
+
+: A valid BCP 47 language tag must be set or the lexical model will
+ not install on your mobile device. Update the list of languages you
+ want to associate with your lexical model.
+
+You could stop here. This would be a completely valid package, but it
+would not be as good as it could be. So let's continue on to the next
+step, and fill in some descriptions of the package.
+
+[Step 4: Filling in package details](step-4)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/distribute/tutorial/step-4.md b/developer/docs/help/guides/lexical-models/distribute/tutorial/step-4.md
new file mode 100644
index 00000000000..bae0be8c676
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/distribute/tutorial/step-4.md
@@ -0,0 +1,55 @@
+---
+title: Step 4: Filling in package details
+---
+
+In the Package Editor, click on the **Details** tab. You should fill in as many details as you can on this page.
+
+![](../../../../images/lm/tutorial_distribute_model_details.png)
+
+Package Name
+
+: The Package Name will be displayed in the package install dialog and
+ wherever the package is referred to.
+
+Model Version
+
+: Update the version (intial version can default to 1.0). A version
+ number for the model and package is important - it helps your users
+ know that they are using the most recent update of your package. The
+ version format you should use is `1.0`.
+
+ When making a major change to your lexical model package, increment
+ the first part and set the second part to `0`, e.g. from `1.0` to
+ `2.0`.
+
+ When making a bug fix or a minor update, increment the second part,
+ e.g. from `1.0` to `1.1`.
+
+ Version numbers should be in the form `major.minor[.subversion]`.
+ Subversion is optional but is helpful for small bug fix releases.
+ Each of the sections of the version should be an integer. Keyman
+ does integer comparisons on the version numbers, so, for example,
+ version `2.01` is regarded as older than version `2.2`. Alphabetic
+ or date formats should be avoided as the installer for the model
+ cannot determine which version is older reliably.
+
+Copyright
+
+: Enter copyright details for your lexical model package. Keep this
+ reasonably short or it won't be clear for end users.
+
+Author
+
+: Enter your name or the name of your company.
+
+Email
+
+: Enter a contact email address where package users can contact you.
+ If you don't want to be contacted via email, leave this field empty
+
+Website
+
+: Enter the name of the website where you will have information about
+ this lexical model.
+
+[Step 5: Compiling, testing and distributing a Package](step-5)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/distribute/tutorial/step-5.md b/developer/docs/help/guides/lexical-models/distribute/tutorial/step-5.md
new file mode 100644
index 00000000000..ed873c69cab
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/distribute/tutorial/step-5.md
@@ -0,0 +1,55 @@
+---
+title: Step 5: Compiling, testing and distributing a Package
+---
+
+In the Package Editor, click on the **Compile** tab.
+
+![](../../../../images/lm/tutorial_distribute_model_compile.png)
+
+Click **Compile Package** to compile the
+package into a .kmp file. Compiling takes all the files listed for the
+package, compresses them (using a .ZIP-compatible format) and adds the
+package information, all into a single file. If any files are not
+available, an error will be listed in the Messages window.
+
+After compiling, you can test the package installation in the mobile
+Keyman apps. You should test that all the model installs successfully,
+that the Welcome file is displayed during the install, and that the
+documentation is accessible to the end user.
+
+## Distributing a package on the Keyman Cloud Lexical Model Repository
+
+Once you have tested the package to your satisfaction, it is time to
+distribute it. We recommend submitting your lexical model package to the
+[Keyman Lexical Models Repository](https://github.com/keymanapp/lexical-models)
+
+## Distributing a package on your own website
+
+If you distribute a package on your own site, we have the following
+recommendations:
+
+1. Ensure the MIME type on the web server or folder for .KMP files is
+ set up to application/octet-stream. Without this, .KMP files may be
+ recognised as .ZIP files -- this is not helpful to the end user as
+ it will be opened in the wrong application.
+2. Avoid putting the .KMP file in an archive (e.g. .ZIP) or
+ self-expanding archive (.EXE) - this makes it harder for end users
+ to install. A .KMP file is already compressed (it is actually just a
+ ZIP archive file!) and you won't save much space by recompressing
+ it.
+3. Include a link to the Keyman download page:
+ `http://keyman.com/downloads/`
+
+## Distributing a package by email
+
+- Attaching the KMP file directly to an email may be blocked for security
+reasons. As mentioned above, a KMP file is basically a ZIP file and
+lexical model data is in JavaScript, this is a combination that looks
+suspicious to many email servers. You can upload it to a Google drive,
+and email a link for downloading the file
+
+## Installing the lexical model package
+
+- The same method for installing custom keyboard packages to mobile
+devices can be used for installing lexical model packages. Refer to
+these pages for [Keyman for iPhone and iPad](../../../distribute/install-kmp-ios) and [Keyman for Android](../../../distribute/install-kmp-android).
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/index.md b/developer/docs/help/guides/lexical-models/index.md
new file mode 100644
index 00000000000..e913ce7007c
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/index.md
@@ -0,0 +1,22 @@
+---
+title: Developing lexical models (dictionaries)
+---
+
+A **lexical model** (Keyman apps use the term
+**dictionary**) is what powers **predictive text**
+and **autocorrect** for a language. If you want your keyboard to predict
+and correct words in your language, you must create a lexical model that
+generates suggestions for your language.
+
+[What is a lexical model? Or: how do I add prediction and autocorrection
+to my keyboard?](intro)
+
+[Tutorial: Developing a lexical model from a word list](tutorial)
+
+[Advanced lexical models topics](advanced)
+
+[Distribute a lexical model](distribute)
+
+## Reference
+
+[Wordlist TSV format](../../reference/file-types/tsv)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/intro/index.md b/developer/docs/help/guides/lexical-models/intro/index.md
new file mode 100644
index 00000000000..314a571ea52
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/intro/index.md
@@ -0,0 +1,162 @@
+---
+title: What is a lexical model?
+---
+
+
+
+![Smartphone keyboard displaying suggestions for the English phrase ”on my w-”](../../../images/lm/whatis-on-my-w.png)
+
+##### Predictive text for English.
+
+
+
+Many mobile phone keyboards enjoy **predictive text** for their
+languages.
+
+Predictive text is the feature on your keyboard that displays a series
+of _predictions_, typically above your keyboard,
+that try to guess the word or words that you are typing next. For
+example, if I start typing the English phrase “On my w”, your keyboard’s
+predictive text feature will infer, using its knowledge of the English
+language, that the word you are typing is most likely “way”, followed by
+other, less likely suggestions, such as “whole”, or “website”.
+
+The same feature that provides predictive text can also suggest
+_corrections_ to what you are typing. For
+example, if I start typing “thr” on my English keyboard, my keyboard
+will suggest that I meant to type “the” instead. This **autocorrect**
+feature is powered by your keyboard’s knowledge of the current language.
+
+The way your keyboard knows how to suggest **predictions** and
+**corrections** for your language is through its **lexical model**.
+
+
+
+![diagram of the context ”on my w” used as input to the lexical model; the results of the lexical model are a list of suggestions.](../../../images/lm/overview.svg)
+
+##### How the lexical model is involved in generating suggestions.
+
+
+
+## Why should I create a lexical model for my language?
+
+### Words are difficult to type
+
+
+
+![Typing “n-a-i-v” on a smartphone. The keyboard suggests “naïve” for this input.](../../../images/lm/whatis-naiv.png)
+
+##### Typing “naiv” on a smartphone with predictive text.
+
+
+
+Some words have many accents, diacritics, or similar-looking forms. This
+is not very common in the English language, however, this is quite
+common in other languages. Predictive text can recognize forms without
+the correct diacritics, or recognize forms that are simpler to type than
+the orthographically correct version. This allows the typist to type
+words quickly, with minimum effort.
+
+For example, in English, I want to type “naïve”, however, my keyboard
+does not have the ï key present on its main
+layout. Sure, if I use a keyboard that I can press-and-hold the
+i key, it may pop-up additional characters,
+and I can probably find ï nestled there
+among the other options. However, I have to go out of my way to type the
+correct variant, whereas the incorrect variant will be perfectly
+understood. In most cases, I choose the option that is more economical
+to type–“naive”—rather than the “correct” option.
+
+However, a **lexical model** that understands the English language will
+see the word “naive” does not exist, but a similarly typed variant
+“naïve” does exist. Therefore, when I type “naive”, the lexical model
+will suggest “naïve”, and I can select it and have the correctly spelled
+version without having to long-press and select the correct “ï”. Even
+better than that, I can choose the suggestion right after typing “naiv”,
+as there are relatively view English words that start with the prefix of
+“naiv-”
+
+### Words are long
+
+
+
+![The top suggestion shows “incomprehensible”](../../../images/lm/whatis-incompr.png)
+
+Typing “incompr” on an English keyboard.
+
+
+
+Sometimes, the words are very long, but can be typed in far fewer
+keystrokes if predictive text is used. For example, if in English, I
+want to write the word “incomprehensible” (a 16-letter word), a lexical
+model for English can predict it from the prefix “incompr” and save 8
+keystrokes!
+
+English is not prone to extraordinarily long words; the average word
+length in English is about 9 letters. However, languages that are fond
+of compounding, such as German (e.g.,
+_scheinwerferreinigungsanlage_ is a 28-letter word
+which means “headlight washers”), or _polysynthetic
+languages_ in which one word can have the meaning of a complete
+sentence in English, such as Mohawk (e.g.,
+_sahonwanhotónkwahse_ is a 20-letter word which
+means “she
+opened the door for him again”). For these languages, predictive
+text can save even more keystrokes than in English.
+
+### People make mistakes
+
+
+
+![The first suggestion is to write the word “the” instead.](../../../images/lm/whatis-correct-thr.png)
+
+##### Typing “thr” on an English keyboard.
+
+
+
+When typing quickly on a small phone screen, mistakes are inevitable.
+Say I try to write the word “the” on my phone. I press
+t , then h , but
+as I try to type e , I press a few
+millimeters to the right of the intended key and press
+r instead. With a lexical model, the
+predictive text feature understands that in English, “thr” is not a
+complete word in-and-of-itself; however, a word that is typed quite
+similarly, “the”, is a very common word. Therefore, the lexical model
+provides enough information to assume that the user intended to type
+“the” instead of “thr”. Thus, one of its **suggestions** is the
+**correction** of “the” in place of “thr”.
+
+However, the predictive text feature is not overly presumptuous; what if
+the typist really did want to type “thr”? As a result, “thr” is
+suggested as a _keep suggestion_. When the typist
+selects the “keep” suggestion, whatever they had originally typed is
+kept, even if the lexical model suggests what it thinks is a far more
+likely correction.
+
+## What do I need to make my own lexical model?
+
+To make a lexical model, you need some information about your language.
+At bare minimum, you need a list of words in your language. Keyman
+Developer supports importing a word list as a spreadsheet of words in
+your language that you wish to use for predictions and corrections. This
+will create a _word list lexical model_.
+
+If you have such a list of words, you can continue to the
+[tutorial](../tutorial) to create a word list lexical model for your
+language!
+
+However, to make a more accurate lexical model, you will need an idea of
+how to rank suggestions relative to each other. For this, you may extend
+the simple word list with **counts**. Each word has a count of how often
+it has been seen in a representative collection of texts in your
+language. For example, I could download articles from the English
+language Wikipedia, and count how often Wikipedia contributors have
+typed “rule” versus how many times they have typed “rupturewort”. This
+will help us understand how to rank these two suggestions given the
+context of “ru”.
+
+You can extend your spreadsheet with counts (placed in the second
+column) to make a more accurate—and thus more useful—lexical model.
+
+[Next: Developing a lexical model from a word list](../tutorial)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/tutorial/index.md b/developer/docs/help/guides/lexical-models/tutorial/index.md
new file mode 100644
index 00000000000..d6887656b3f
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/tutorial/index.md
@@ -0,0 +1,29 @@
+---
+title: Tutorial: Developing a lexical model from a word list
+---
+
+[Step 1: Prerequisites for building a lexical model](step-1)
+
+[Step 2: Creating a lexical model project](step-2)
+
+[Step 3: Get some language data](step-3)
+
+[Step 4: Compile the lexical model](step-4)
+
+## Overview
+
+In this tutorial, will learn how to create a **word list lexical
+model**. If you are not sure what a lexical model is, or why you would
+make one, read [“What is a lexical model?”](../intro) first, then come
+back here to follow this tutorial.
+
+## Let's begin
+
+Let's get started! Move on to the next topic to begin the first step.
+
+At the bottom of each page in the tutorial, will be a link to the next
+step. You can use these links to work your way through the tutorial. You
+may also find links to reference information, which you can select to
+learn more about a particular aspect of creating Keyman keyboards.
+
+[Step 1: Prerequisites for building a lexical model](step-1)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/tutorial/step-1.md b/developer/docs/help/guides/lexical-models/tutorial/step-1.md
new file mode 100644
index 00000000000..9bd7f84c86f
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/tutorial/step-1.md
@@ -0,0 +1,22 @@
+---
+title: Step 1: Prerequisites for building a lexical model
+---
+
+To prepare a lexical model based on a word list, we will need a few
+things:
+
+- Install [Keyman Developer](https://keyman.com/developer) 12.0 or greater (Windows only)
+- A list of words in your language, stored in an spreadsheet, in dictionary-making software or, other means.
+
+> ### Note
+Currently, lexical models work better with languages that use regular
+spaces as word boundaries. For complex scripts such as Khmer, Thai, Lao,
+etc, a pre-processing is required before getting it to work, i.e. word
+segmentation has to be determined and/or configured. See the advanced
+topic on making a custom [word breaker](../advanced/word-breaker).
+This will be addressed in a future
+[feature](https://github.com/keymanapp/keyman/issues/5025).
+
+Now to create a lexical model project!
+
+[Step 2: Creating a lexical model project](step-2)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/tutorial/step-2.md b/developer/docs/help/guides/lexical-models/tutorial/step-2.md
new file mode 100644
index 00000000000..bdfb063ae46
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/tutorial/step-2.md
@@ -0,0 +1,115 @@
+---
+title: Step 2: Creating a lexical model project
+---
+
+## Create the new project
+
+Start Keyman Developer. On the “Welcome” screen, click on
+**New Project...**. The “New Project” dialog
+will appear. Select “Wordlist Lexical Model” and press
+**OK**.
+
+
+
+![“New Project” dialog](../../../images/ui/frmNewLMProject.png)
+
+###### The “New Project” dialog, with “Wordlist Lexical Model” selected.
+
+
+
+## Provide required information
+
+
+
+![New LM Project Parameters](../../../images/ui/frmNewLMProjectParameters.png)
+
+###### The New Lexical Model dialog box.
+
+
+
+The “New Wordlist Lexical Model Project” dialog will appear.
+
+To make sharing your lexical model easier, a project needs the following
+information:
+
+Author Name
+: This is either your **full name** or the **organization** you're
+ creating a model for. In this example, I am creating a lexical model
+ on behalf of my organization, the National Research Council Canada,
+ so I write that as the author name.
+
+Model Name
+: We recommend the name of the **language**, **dialect**, or
+ **community** that this model is intended for. The name must be
+ written in all the Latin letters or Arabic numerals. In this
+ example, we're creating a language model for SENĆOŦEN, so we use the
+ model name `Sencoten`.
+
+### Provide auxiliary information
+
+The following information is also required, but most users will use
+**default values**.
+
+Copyright
+: Who owns the rights to this model and its data? This field should
+ contain the word "Copyright", the copyright symbol "©", and the full name of the rights
+ owner. Do not put the year of copyright in this field (see Full Copyright). Typically,
+ you can use the automatically generated default value: *Copyright © **Your Full
+ Name or Your Organization***.
+
+Version
+: If this is the first time you've created a lexical model for you
+ language, you should leave the version as **1.0**. Otherwise, your
+ version number must conform to the following rules: A version string
+ made of `major revision number`.`minor revision number`.
+
+## Determine your language's BCP 47 language tag
+
+Keyman needs to know how to link your model to the appropriate keyboard
+layout, so that they can both work together. To do this, Keyman utilizes
+[BCP 47](../../../reference/bcp-47) language tags.
+
+To add a language tag, click the **Add**
+button to bring up the “Select BCP 47 Tag” dialog box.
+
+
+
+![Select BCP 47 Tag dialog](../../../images/ui/frmNewLMProjectSelectLanguage.png)
+
+###### The “Select BCP 47 Tag” dialog box for SENĆOŦEN.
+
+
+
+Enter the BCP 47 language tag that you have selected. [Learn more about BCP 47 language tags](../../../reference/bcp-47)
+
+Once you are finished adding the primary language, click
+**OK** to return to the **New Lexical Model
+Project dialog**.
+
+## The Model ID
+
+Keyman will create a **model ID** which is how
+Keyman sorts and organizes different lexical models. If you choose to
+share your model publicly, the model ID is vital for both people and
+Keyman to identify and use your lexical model!
+
+Keyman automatically generates a model ID for you, given all the
+information already filled out. If you're satisfied with the generated
+model ID, you can [skip to the next step](#toc-double-check-the-information).
+
+In this example, my generated model ID is
+`national_research_council_canada.str.sencoten`, derived from my
+organization name, the name of the primary language, and my model name.
+However, I find the “author ID” part of the generated model ID
+excessively long. I changed the author ID to `nrc`, and the model ID
+automatically changes to the much more manageable `nrc.str.sencoten`.
+
+## Double-check the information
+
+Verify that all of the information is correct. Once all of the required
+information has been filled in and verified, click
+**OK** to create the project.
+
+Once we have created the project, we can begin to prepare the data!
+
+[Step 3: Get some language data](step-3)
diff --git a/developer/docs/help/guides/lexical-models/tutorial/step-3.md b/developer/docs/help/guides/lexical-models/tutorial/step-3.md
new file mode 100644
index 00000000000..88d9af90aec
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/tutorial/step-3.md
@@ -0,0 +1,143 @@
+---
+title: Step 3: Get some language data
+---
+
+To predict words in your language, a lexical model needs to know the
+words in your language!
+
+Keyman Developer understands how to read words in a [TSV file](../../../reference/file-types/tsv). This kind of file can be saved
+from a **spreadsheet** application like [Google Sheets](https://sheets.google.com/) or [Microsoft Excel](https://products.office.com/en/excel). Other users may also use
+**language data management software** like [SIL FieldWorks Language
+Explorer (FLEx)](https://software.sil.org/fieldworks/) to export an
+appropriate **TSV** file.
+
+One simple way to create your TSV file is to use the **PrimerPrep**
+tool:
+
+1. Install PrimerPrep (info at
+ )
+2. Run PrimerPrep (note that on the first run it often takes a couple
+ of minutes; subsequent starts are faster)
+3. Click on the Add Text(s) button; select one or more plain text
+ (UTF-8) files in the language to analyze
+4. The word list with frequency counts appears in the pane to the right
+5. From the File menu, select Save Word List… and specify the file name
+ and location (a .tsv extension is recommended)
+
+> **For advanced users**: Ultimately, what Keyman Developer requires is a
+tab-separated values (TSV) file in a specfic format described in the
+[file types reference](../../../reference/file-types/tsv). Refer to this
+reference file if you are coding your own exporter.
+
+## Example Wordlist
+
+I have words in my language of choice, SENĆOŦEN. Here is my list of
+words, with the count of how many times I’ve seen the word:
+
+List of ten SENĆOŦEN words, with counts
+
+
+| Word | Count |
+|-------|-------|
+| TŦE | 13644 |
+| E | 9134 |
+| SEN | 4816 |
+| Ȼ | 3479 |
+| SW̱ | 2621 |
+| NIȽ | 2314 |
+| U¸ | 2298 |
+| I¸ | 1988 |
+| ȻSE | 1925 |
+| I | 1884 |
+
+
+## Editing the .TSV in Keyman Developer
+
+If you plan to only edit a few entries for your wordlist, you can use
+the TSV editor in Keyman Developer. The project template created a
+wordlist named **wordlist.tsv** that we will now edit.
+
+In Keyman Developer project view, select the Models tab and click on
+wordlist.tsv.
+
+
+
+![Open TSV file in Keyman Developer](../../../images/lm/developer-open-tsv.png)
+
+##### Open a .tsv file in Keyman Developer
+
+
+
+Keyman Developer already generated a few example words when it created
+the template wordlist.tsv file.
+
+
+
+![Example wordlist.tsv generated by template](../../../images/lm/template-tsv.png)
+
+##### Template wordlist.tsv
+
+
+
+We will replace these entries with SENĆOŦEN words from our wordlist. For
+each row, edit the "Word Form" and "Count". Counts are optional for each
+word: that is, some words may specify counts in the second column, while
+other words may leave the second column blank. To create a new entry at
+the bottom, click "Add word...". When you are finished, you'll have a
+wordlist that looks like this:
+
+
+
+![Editing wordlist.tsv in Keyman Developer](../../../images/lm/edited-tsv.png)
+
+##### Edited wordlist.tsv for SENĆOŦEN
+
+
+
+After saving your wordlist file, you can move on to Step 4.
+
+## Editing the .TSV in Google Sheets
+
+Alternatively, you may want to use a different spreadsheet tool for
+editing large wordlists. I’ve entered this information into my
+spreadsheet of choice, [Google Sheets](https://sheets.google.com/). I’ve
+shared this spreadsheet publicly
+[here](https://docs.google.com/spreadsheets/d/10zhIc439BCSSooL_-HeJ6TUHd-ovkiXYcIGe-pHDTSg/edit?usp=sharing).
+The order of the columns matters:
+
+The first column (column A) **must** be the “words”. If provided, the
+second column (column B) **must** be the “counts”. Counts are optional
+for each word: that is, some words may specify counts in the second
+column, while other words may leave the second column blank. The third
+column (column C) is always ignored. You may use this column as a
+comment. The spreadsheet can be as simple as a single column of all of
+the words in the language, with each word being separated by a line
+break.
+
+This is what my word list looks like in Google Sheets:
+
+
+
+![screenshot of the word list in Google Sheets](../../../images/lm/sencoten-sheets-full.png)
+
+##### The word list, as it appears in Google Sheets
+
+
+
+Now, we download the spreadsheet in the [required format](../../../reference/file-types/tsv). To do this, in Google
+Sheets, select “File” » “Download as” » “Tab-separated values (.tsv,
+current sheet)”.
+
+
+
+![screenshot of “Save as…” menu in Google Sheets, selecting ”TSV”](../../../images/lm/sencoten-sheets-save-as.png)
+
+##### Exporting the TSV file from Google Sheets
+
+
+
+I’ll save mine as **wordlist.tsv**.
+
+Now that we have our word list, let's compile our model!
+
+[Step 4: Compiling the lexical model](step-4)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/tutorial/step-4.md b/developer/docs/help/guides/lexical-models/tutorial/step-4.md
new file mode 100644
index 00000000000..28867a3afc9
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/tutorial/step-4.md
@@ -0,0 +1,34 @@
+---
+title: Step 4: Compile the lexical model
+---
+
+Before use the lexical model, we must compile it. In this step, the
+`.tsv` wordlist and [the model definition file](../advanced/model-definition-file) get
+_compiled_ into a single `.model.js` file. This
+file is the one that Keyman uses internally to generate suggestions.
+Later on, we will bundle the `.model.js` file into a lexical model
+package so that Keyman apps can install the lexical model.
+
+## Models tab
+
+In Keyman Developer project view, select the "Models" tab.
+
+
+
+
+
+Click on the **Build models** button to
+compile the lexical model. The Message window will display the results
+of the compilation. If you have no typing errors, the lexical model
+should compile successfully. If successful, this will create a
+`.model.js` file in a build subdirectory of the lexical model project
+directory.
+
+## Next steps
+
+This concludes the lexical model tutorial. Here's what's next:
+
+- Now that the model is built, we are ready to [distribute our lexical model](../distribute/packages).
+- (*For advanced users*) We can do some [advanced customization](../advanced/) by modifying the [model definition file](../advanced/model-definition-file). Advanced customizations
+ require a some comfort with modifying code!
\ No newline at end of file
diff --git a/developer/docs/help/guides/test/index.md b/developer/docs/help/guides/test/index.md
new file mode 100644
index 00000000000..4dac01ec933
--- /dev/null
+++ b/developer/docs/help/guides/test/index.md
@@ -0,0 +1,10 @@
+---
+title: Testing keyboards
+---
+
+- [How to test your keyboard layout — touch and
+ desktop](keyboard-touch-and-desktop)
+
+# Testing lexical models
+
+- [How to test your lexical model](lexical-model)
diff --git a/developer/docs/help/guides/test/keyboard-touch-and-desktop.md b/developer/docs/help/guides/test/keyboard-touch-and-desktop.md
new file mode 100644
index 00000000000..33900238911
--- /dev/null
+++ b/developer/docs/help/guides/test/keyboard-touch-and-desktop.md
@@ -0,0 +1,151 @@
+---
+title: How to test your keyboard layout — touch and desktop
+---
+
+Keyman Developer includes full touch layout editing tools. In the image
+below I am editing a Khmer Angkor touch layout sample.
+
+[![Touch Editor -
+Khmer](../../images/testing/touch-editor-khmer-800wi.png "Touch Editor - Khmer")](../../images/testing/touch-editor-khmer.png)
+
+Once you have created your keyboard layout, you need to test it. Your
+keyboard layout may cover desktops as well as touch devices. The
+**Build** tab of the keyboard editor gives you access to testing and
+debugging commands for all platforms.
+
+[![Touch Editor - Build
+tab](../../images/testing/touch-editor-build-800wi.png "Touch Editor - Build tab")](../../images/testing/touch-editor-build.png)
+
+Testing is easy on Windows: press the **Start Debugging** button on the
+**Build** tab to test the rules in your keyboard layout with a [fully
+interactive debugger](../../context/debug). This allows you to step
+through complex rules, inspecting the contents of the stores referenced
+in each rule, and examine the context, deadkey state and output at each
+point.
+
+After validating your rules, you will want to test the [on screen
+keyboard](../../context/keyboard-editor#toc-on-screen-tab) and how your
+keyboard feels within other applications in Windows. We recommend
+[creating a package](../distribute/tutorial) in order to simplify the
+installation of both the on screen keyboard and the keyboard (which are
+stored as two separate files). Then click the **Install** button in
+**Build** tab of the package wizard and you are done.
+
+## Preparing to test
+
+But how do you test your touch layout on your iPhone, iPad or Android
+device? We've thought about this as well. Keyman Developer includes a
+web server specifically for testing and installing keyboard layouts on
+these devices.
+
+To start the web server and debugging session, on the **Build** tab of
+your keyboard editor, click **Test Keyboard on web** (we may rename
+these buttons in a later release to reflect the cross-platform targets
+here).
+
+You may need to open up the host port on your local computer firewall;
+most firewall software will prompt you at this point. The default port
+that Keyman Developer uses is 8008. You can change this in the
+**Tools**/**Options**/**Debugger** dialog if necessary.
+
+You will see a list of web addresses in the list box below the **Test
+Keyboard on web** label. This lists all the computer and domain names
+and IP addresses that Keyman Developer is listening on that we can find.
+Why so many? Because of the variety of network setups, different users
+will need to use different addresses; Keyman Developer leaves that up to
+you! We call the list the **Debug Host List**.
+
+![KeymanWeb Debug
+Host](../../images/testing/startdebugging-kd10.png "KeymanWeb Debug Host")
+
+## Testing your web keyboard on your desktop
+
+You can test your desktop layout by selecting an address in the debug
+host list and clicking **Open debugger in local browser**. This will
+load the KeymanWeb Debug Host page in your browser, and you should be
+able to select your keyboard from the list and start using it.
+
+## Testing your keyboard on a touch device
+
+To start testing on your mobile device, your device needs to be on the
+same network as your PC. Then open your web browser on the device. Which
+browser? On iOS, use Safari. On Android devices, we recommend using
+Chrome. We don't recommend the Android Browser, because its capabilities
+vary dramatically depending on the Android version and the brand of your
+device.
+
+Then you will need to pick an address from the debug host list in Keyman
+Developer that your device can find. An IP address on the same network
+is almost always going to work, but you may find that a canonical domain
+name works as well, if you are in an environment with a name server. The
+localhost and 127.0.0.1 addresses are local to your PC -- so don't try
+those.
+
+Enter the address into the URL bar on your browser; don't forget to type
+the **:8008** port at the end of the address you choose (on most devices
+you can skip the **http://** at the start):
+
+Alternatively, you can click the **Send addresses to email...** button.
+It sends a list of the debug host addresses to an email address of your
+choice, so you can then just click the link in your email on your target
+device.
+
+![Enter the debug host
+URL](../../images/testing/frame/android-enter-debug-host.png "Enter the debug host URL")
+
+All going well, you should be presented with a page like this:
+
+![Viewing the debug host on
+iPhone](../../images/testing/frame/android-debug-host.png "Viewing the debug host on iPhone")
+
+If you get a Host Not Found error, try different addresses from the
+**KeymanWeb Debug Host** list, check your computer's firewall settings,
+and make sure that both devices are on the same network.
+
+Now you can instantly test your layout in KeymanWeb Touch, by clicking
+in the blank edit box: the touch layout will appear. If you make changes
+in Keyman Developer, just recompile (**Compile Keyboard** button, or
+F7), and reload on the touch device -- you don't need to click the
+**Test Keyboard on web** button again.
+
+![Testing a keyboard on
+iPhone](../../images/testing/frame/android-debug-keyboard.png "Testing a keyboard on iPhone")
+
+## Loading your keyboard into the native Keyman apps
+
+If you do not have Keyman currently installed on your target device, you
+can click **Install Keyman for Android** or **Install Keyman for iOS**
+to bring up the appropriate store for installing Keyman.
+
+Finally, you can also install your keyboard layout directly into Keyman
+for Android or Keyman for iPhone and iPad by clicking the **Add keyboard
+to Keyman for Android** button or **Add keyboard to Keyman for iOS**
+button on the test page on your device.
+
+![Installing the keyboard into native
+app](../../images/testing/frame/installing-native-keyboard-1.png "Installing the keyboard into native app")
+
+![Installing the keyboard into native
+app](../../images/testing/frame/installing-native-keyboard-2.png "Installing the keyboard into native app")
+
+Now your keyboard layout is installed and available in the Keyman App,
+and if you have the Keyman version with the system keyboard support
+installed, across all apps.
+
+You can test multiple keyboards just by clicking **Test Keyboard on
+web** in each keyboard editor.
+
+## Related articles
+
+Other articles on developing touch layouts:
+
+- [Creating a touch keyboard layout for Amharic - part
+ 1](../develop/creating-a-touch-keyboard-layout-for-amharic)
+- [Creating a touch keyboard layout for Amharic - the nitty
+ gritty](../develop/creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty)
+
+You can distribute your keyboard to other users by following the
+instructions in this article:
+
+- [Distribute keyboards to Keyman
+ applications](../distribute/packages)
diff --git a/developer/docs/help/guides/test/keyboard-touch-mobile-emulator.md b/developer/docs/help/guides/test/keyboard-touch-mobile-emulator.md
new file mode 100644
index 00000000000..fb68b11f2b2
--- /dev/null
+++ b/developer/docs/help/guides/test/keyboard-touch-mobile-emulator.md
@@ -0,0 +1,61 @@
+---
+title: How to test your touch layout in the Google Chrome mobile emulator
+---
+
+This post builds on knowledge in another article about [testing Keyman
+touch layouts](keyboard-touch-and-desktop). Now, we provide an
+alternative test platform for your keyboards, running on your desktop
+computer rather than your touch device.
+
+Google Chrome includes a mobile emulator for web pages, for various
+devices including iPhones, iPads and Androids. Recent builds of
+KeymanWeb work well with this emulator and this provides a quicker and
+easier way, in many cases, to get a feel for the layout of your keyboard
+and do rapid testing.
+
+It is still essential to test your layout on a real device as well as
+the emulator: the feel of clicking on keys with a mouse is very
+different, and gestures such as longpress menus will feel awkward on the
+emulator. We use both native mobile and emulator testing when building
+our layouts, switching between the two frequently. Some of the emulated
+devices emulated may or not work as well a real device.
+
+## Test Process
+
+1. Start testing your keyboard with the [original steps on testing
+ touch keyboards](keyboard-touch-and-desktop).
+ ![](../../images/testing/startdebugging-kd10.png "StartDebugging")
+2. Open Chrome, navigate to the debug host page for your keyboard, and
+ press F12.
+ ![Chrome1](../../images/testing/chrome1.png "Chrome1")
+3. In the Developer Tools window, click the mobile icon:
+ ![Chrome2](../../images/testing/chrome2.png "Chrome2")
+4. Back in the debug host page, select the appropriate device from the
+ mobile emulation toolbar, then press F5 to reload and enable the
+ touch mode. Some recommended devices are iPad or iPad Pro for tablet
+ testing, and iPhone X or Nexus 6P for mobile testing. Do not select
+ "Responsive".
+ ![Chrome3](../../images/testing/chrome3.png "Chrome3")
+5. At this point, you should see a simulation of selected device's
+ screen, and KeymanWeb should be presenting its touch keyboard rather
+ than the desktop equivalent. You'll also see the mouse cursor has
+ turned into a fuzzy circle, to simulate a fingertip instead of a
+ precise arrow.
+ ![Chrome4"](../../images/testing/chrome4.png "Chrome4")
+
+You can now test the keyboard layout and get an idea of how it will feel
+on a real touch device. Rotation, or changing device types will require
+a page load (F5) in order to render correctly.
+
+- [Creating a touch keyboard layout for Amharic - part
+ 1](../develop/creating-a-touch-keyboard-layout-for-amharic)
+- [Creating a touch keyboard layout for Amharic - the nitty
+ gritty](../develop/creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty)
+- [How to test your keyboard layout — touch and
+ desktop](keyboard-touch-and-desktop)
+
+You can distribute your keyboard to other users by following the
+instructions in this article:
+
+- [Distribute keyboards to Keyman
+ applications](../distribute/packages)
diff --git a/developer/docs/help/guides/test/lexical-model.md b/developer/docs/help/guides/test/lexical-model.md
new file mode 100644
index 00000000000..e46d9a262dd
--- /dev/null
+++ b/developer/docs/help/guides/test/lexical-model.md
@@ -0,0 +1,67 @@
+---
+title: How to test your lexical model
+---
+
+It is helpful to be familiar with processes for testing touch keyboards
+before starting to test a lexical model. This tutorial builds on
+knowledge found in the following two articles:
+
+- [Testing keyboard layouts on devices](keyboard-touch-and-desktop)
+
+## Test Process
+
+Lexical models are tested in Keyman Developer's web testing environment.
+They can be loaded on any device that can access the testing web site,
+in the same way as testing a keyboard layout.
+
+A lexical model must be tested in conjunction with a keyboard. The
+keyboard provides the input, and the lexical model will present
+suggestions based on that input. In normal use, a lexical model is
+linked to a keyboard by the BCP 47 language code with which they are
+both registered. However, within the debug environment, any lexical
+model can be tested with any keyboard -- but of course an Arabic lexical
+model will not provide useful suggestions with a Hindi keyboard, so you
+still need a keyboard which will provide sensible input to the model.
+
+There are two ways to load a lexical model in the web-based test
+environment:
+
+1. The first option is to start testing a keyboard as you would, in the
+ web test environment. The keyboard will remain available within the
+ web test environment until Keyman Developer is restarted, even if
+ you switch projects.
+2. The second way is to specify a compiled .js keyboard file for the
+ lexical model test to load when you start testing, in the **Build**
+ tab of the Lexical Model Editor. This keyboard can be selected once
+ and remains saved in your project user settings for future tests.
+
+To start testing a lexical model, follow the steps below:
+
+1. Ensure the model is compiled by pressing F7 within the lexical model
+ editor, or pressing the **Compile Model** button within the
+ **Build** tab.
+2. Prepare the associated keyboard for testing as you normally would,
+ and load it in the web test environment following the instructions
+ above. In the example below, I have selected the fv_sencoten
+ keyboard to use during my lexical model test (you may alternatively
+ choose to load the keyboard project and start testing from there).
+ ![](../../images/lm/test-model.png "model-test-page")
+3. Choose the URL in the web addresses list as shown in the image
+ above. Keyman Developer lists all the addresses on your computer
+ that it can find; some will be addressable by other devices, where
+ as addresses such as 'localhost' or '127.0.0.1' are only visible on
+ the local computer. Typically, any other device must still be on the
+ same network, and you must make sure you allow Keyman Developer
+ through your local computer firewall for port 8008 (the default
+ debugging port).
+4. Once you start testing, you can swap between models from the
+ **Models** menu in the test web page. As you type on the touch
+ keyboard, you should see suggestions change in the banner, and you
+ should be able to select the suggestions. You can also do the same
+ testing in a web browser on a mobile device. Note: you can point
+ your mobile device camera at the QRCode on screen to quickly load
+ the selected URL on your mobile device.
+
+In order to test the lexical model inside Keyman on a mobile device, you
+will need to [prepare the model file for
+distribution](../lexical-models/distribute).
diff --git a/developer/docs/help/images/app/dist-file-browser-ap.png b/developer/docs/help/images/app/dist-file-browser-ap.png
new file mode 100644
index 00000000000..e0957ac4dfe
Binary files /dev/null and b/developer/docs/help/images/app/dist-file-browser-ap.png differ
diff --git a/developer/docs/help/images/app/dist-install1-ap.png b/developer/docs/help/images/app/dist-install1-ap.png
new file mode 100644
index 00000000000..9150ea481a7
Binary files /dev/null and b/developer/docs/help/images/app/dist-install1-ap.png differ
diff --git a/developer/docs/help/images/app/dist-install1-i.png b/developer/docs/help/images/app/dist-install1-i.png
new file mode 100644
index 00000000000..e4632eafc93
Binary files /dev/null and b/developer/docs/help/images/app/dist-install1-i.png differ
diff --git a/ios/help/ios_images/dist-kmp-open-i.png b/developer/docs/help/images/app/dist-kmp-open-i.png
similarity index 100%
rename from ios/help/ios_images/dist-kmp-open-i.png
rename to developer/docs/help/images/app/dist-kmp-open-i.png
diff --git a/ios/help/ios_images/dist-kmp-success-i.png b/developer/docs/help/images/app/dist-kmp-success-i.png
similarity index 100%
rename from ios/help/ios_images/dist-kmp-success-i.png
rename to developer/docs/help/images/app/dist-kmp-success-i.png
diff --git a/developer/docs/help/images/app/dist-storage-permission-ap.png b/developer/docs/help/images/app/dist-storage-permission-ap.png
new file mode 100644
index 00000000000..0deb5cf41af
Binary files /dev/null and b/developer/docs/help/images/app/dist-storage-permission-ap.png differ
diff --git a/developer/docs/help/images/app/dist-url-screen-ap.png b/developer/docs/help/images/app/dist-url-screen-ap.png
new file mode 100644
index 00000000000..bf15950a5b1
Binary files /dev/null and b/developer/docs/help/images/app/dist-url-screen-ap.png differ
diff --git a/ios/help/ios_images/dist-url-screen-i.png b/developer/docs/help/images/app/dist-url-screen-i.png
similarity index 100%
rename from ios/help/ios_images/dist-url-screen-i.png
rename to developer/docs/help/images/app/dist-url-screen-i.png
diff --git a/developer/docs/help/images/app/dist-welcome-ap.png b/developer/docs/help/images/app/dist-welcome-ap.png
new file mode 100644
index 00000000000..39dadf470d2
Binary files /dev/null and b/developer/docs/help/images/app/dist-welcome-ap.png differ
diff --git a/ios/help/ios_images/dist-welcome-i.png b/developer/docs/help/images/app/dist-welcome-i.png
similarity index 100%
rename from ios/help/ios_images/dist-welcome-i.png
rename to developer/docs/help/images/app/dist-welcome-i.png
diff --git a/developer/docs/help/images/app/settings-language-ap.png b/developer/docs/help/images/app/settings-language-ap.png
new file mode 100644
index 00000000000..e6b2ac4c9d0
Binary files /dev/null and b/developer/docs/help/images/app/settings-language-ap.png differ
diff --git a/developer/docs/help/images/dist-file-browser-ap.png b/developer/docs/help/images/dist-file-browser-ap.png
new file mode 100644
index 00000000000..e0957ac4dfe
Binary files /dev/null and b/developer/docs/help/images/dist-file-browser-ap.png differ
diff --git a/developer/docs/help/images/dist-install1-ap.png b/developer/docs/help/images/dist-install1-ap.png
new file mode 100644
index 00000000000..9150ea481a7
Binary files /dev/null and b/developer/docs/help/images/dist-install1-ap.png differ
diff --git a/developer/docs/help/images/dist-install1-i.png b/developer/docs/help/images/dist-install1-i.png
new file mode 100644
index 00000000000..e4632eafc93
Binary files /dev/null and b/developer/docs/help/images/dist-install1-i.png differ
diff --git a/developer/docs/help/images/dist-kmp-open-i.png b/developer/docs/help/images/dist-kmp-open-i.png
new file mode 100644
index 00000000000..a6220a38562
Binary files /dev/null and b/developer/docs/help/images/dist-kmp-open-i.png differ
diff --git a/developer/docs/help/images/dist-kmp-success-i.png b/developer/docs/help/images/dist-kmp-success-i.png
new file mode 100644
index 00000000000..af1108946b9
Binary files /dev/null and b/developer/docs/help/images/dist-kmp-success-i.png differ
diff --git a/developer/docs/help/images/dist-storage-permission-ap.png b/developer/docs/help/images/dist-storage-permission-ap.png
new file mode 100644
index 00000000000..0deb5cf41af
Binary files /dev/null and b/developer/docs/help/images/dist-storage-permission-ap.png differ
diff --git a/developer/docs/help/images/dist-url-screen-ap.png b/developer/docs/help/images/dist-url-screen-ap.png
new file mode 100644
index 00000000000..bf15950a5b1
Binary files /dev/null and b/developer/docs/help/images/dist-url-screen-ap.png differ
diff --git a/developer/docs/help/images/dist-url-screen-i.png b/developer/docs/help/images/dist-url-screen-i.png
new file mode 100644
index 00000000000..1c002964675
Binary files /dev/null and b/developer/docs/help/images/dist-url-screen-i.png differ
diff --git a/developer/docs/help/images/dist-welcome-ap.png b/developer/docs/help/images/dist-welcome-ap.png
new file mode 100644
index 00000000000..8d753979b12
Binary files /dev/null and b/developer/docs/help/images/dist-welcome-ap.png differ
diff --git a/developer/docs/help/images/dist-welcome-i.png b/developer/docs/help/images/dist-welcome-i.png
new file mode 100644
index 00000000000..cfa0cc725fc
Binary files /dev/null and b/developer/docs/help/images/dist-welcome-i.png differ
diff --git a/developer/docs/help/images/ipa.gif b/developer/docs/help/images/ipa.gif
new file mode 100644
index 00000000000..9d861961f17
Binary files /dev/null and b/developer/docs/help/images/ipa.gif differ
diff --git a/developer/docs/help/images/lm/developer-open-tsv.png b/developer/docs/help/images/lm/developer-open-tsv.png
new file mode 100644
index 00000000000..4a13865b76b
Binary files /dev/null and b/developer/docs/help/images/lm/developer-open-tsv.png differ
diff --git a/developer/docs/help/images/lm/edited-tsv.png b/developer/docs/help/images/lm/edited-tsv.png
new file mode 100644
index 00000000000..e60c291eb29
Binary files /dev/null and b/developer/docs/help/images/lm/edited-tsv.png differ
diff --git a/developer/docs/help/images/lm/overview.svg b/developer/docs/help/images/lm/overview.svg
new file mode 100644
index 00000000000..5259abfd5c3
--- /dev/null
+++ b/developer/docs/help/images/lm/overview.svg
@@ -0,0 +1 @@
+Lexical Model knowledge of your language and its spelling rules on my w context way website “w” suggestions
\ No newline at end of file
diff --git a/developer/docs/help/images/lm/sencoten-sheets-full.png b/developer/docs/help/images/lm/sencoten-sheets-full.png
new file mode 100644
index 00000000000..9ac2d487ff6
Binary files /dev/null and b/developer/docs/help/images/lm/sencoten-sheets-full.png differ
diff --git a/developer/docs/help/images/lm/sencoten-sheets-save-as.png b/developer/docs/help/images/lm/sencoten-sheets-save-as.png
new file mode 100644
index 00000000000..dcb879551d7
Binary files /dev/null and b/developer/docs/help/images/lm/sencoten-sheets-save-as.png differ
diff --git a/developer/docs/help/images/lm/template-tsv.png b/developer/docs/help/images/lm/template-tsv.png
new file mode 100644
index 00000000000..d480c29912a
Binary files /dev/null and b/developer/docs/help/images/lm/template-tsv.png differ
diff --git a/developer/docs/help/images/lm/test-model.png b/developer/docs/help/images/lm/test-model.png
new file mode 100644
index 00000000000..740bc648b0c
Binary files /dev/null and b/developer/docs/help/images/lm/test-model.png differ
diff --git a/developer/docs/help/images/lm/tutorial_distribute_model_3.png b/developer/docs/help/images/lm/tutorial_distribute_model_3.png
new file mode 100644
index 00000000000..2bc7bbad8c4
Binary files /dev/null and b/developer/docs/help/images/lm/tutorial_distribute_model_3.png differ
diff --git a/developer/docs/help/images/lm/tutorial_distribute_model_3_files.png b/developer/docs/help/images/lm/tutorial_distribute_model_3_files.png
new file mode 100644
index 00000000000..417a9dd49e8
Binary files /dev/null and b/developer/docs/help/images/lm/tutorial_distribute_model_3_files.png differ
diff --git a/developer/docs/help/images/lm/tutorial_distribute_model_compile.png b/developer/docs/help/images/lm/tutorial_distribute_model_compile.png
new file mode 100644
index 00000000000..b302e26e9b4
Binary files /dev/null and b/developer/docs/help/images/lm/tutorial_distribute_model_compile.png differ
diff --git a/developer/docs/help/images/lm/tutorial_distribute_model_details.png b/developer/docs/help/images/lm/tutorial_distribute_model_details.png
new file mode 100644
index 00000000000..bf506cac906
Binary files /dev/null and b/developer/docs/help/images/lm/tutorial_distribute_model_details.png differ
diff --git a/developer/docs/help/images/lm/whatis-correct-thr.png b/developer/docs/help/images/lm/whatis-correct-thr.png
new file mode 100644
index 00000000000..28bf1fa82c1
Binary files /dev/null and b/developer/docs/help/images/lm/whatis-correct-thr.png differ
diff --git a/developer/docs/help/images/lm/whatis-incompr.png b/developer/docs/help/images/lm/whatis-incompr.png
new file mode 100644
index 00000000000..02d680a7c61
Binary files /dev/null and b/developer/docs/help/images/lm/whatis-incompr.png differ
diff --git a/developer/docs/help/images/lm/whatis-naiv.png b/developer/docs/help/images/lm/whatis-naiv.png
new file mode 100644
index 00000000000..e8061682337
Binary files /dev/null and b/developer/docs/help/images/lm/whatis-naiv.png differ
diff --git a/developer/docs/help/images/lm/whatis-on-my-w.png b/developer/docs/help/images/lm/whatis-on-my-w.png
new file mode 100644
index 00000000000..d01d216ed68
Binary files /dev/null and b/developer/docs/help/images/lm/whatis-on-my-w.png differ
diff --git a/developer/docs/help/images/settings-language-ap.png b/developer/docs/help/images/settings-language-ap.png
new file mode 100644
index 00000000000..e6b2ac4c9d0
Binary files /dev/null and b/developer/docs/help/images/settings-language-ap.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_1.png b/developer/docs/help/images/simpleTouchKeyboard_1.png
new file mode 100644
index 00000000000..1f5610d6541
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_1.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_10.png b/developer/docs/help/images/simpleTouchKeyboard_10.png
new file mode 100644
index 00000000000..460e1dbae97
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_10.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_11.png b/developer/docs/help/images/simpleTouchKeyboard_11.png
new file mode 100644
index 00000000000..25af1e721f0
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_11.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_12-2.png b/developer/docs/help/images/simpleTouchKeyboard_12-2.png
new file mode 100644
index 00000000000..946fc499008
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_12-2.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_12.png b/developer/docs/help/images/simpleTouchKeyboard_12.png
new file mode 100644
index 00000000000..4b945093a14
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_12.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_13.png b/developer/docs/help/images/simpleTouchKeyboard_13.png
new file mode 100644
index 00000000000..5277c515886
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_13.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_14.png b/developer/docs/help/images/simpleTouchKeyboard_14.png
new file mode 100644
index 00000000000..b73483c39b4
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_14.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_15.png b/developer/docs/help/images/simpleTouchKeyboard_15.png
new file mode 100644
index 00000000000..a1b619e318b
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_15.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_15b.png b/developer/docs/help/images/simpleTouchKeyboard_15b.png
new file mode 100644
index 00000000000..86d1c67d9a0
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_15b.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_16.png b/developer/docs/help/images/simpleTouchKeyboard_16.png
new file mode 100644
index 00000000000..4e730d33695
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_16.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_16b.png b/developer/docs/help/images/simpleTouchKeyboard_16b.png
new file mode 100644
index 00000000000..7dd5feab6b9
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_16b.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_17.png b/developer/docs/help/images/simpleTouchKeyboard_17.png
new file mode 100644
index 00000000000..621809ce215
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_17.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_18.png b/developer/docs/help/images/simpleTouchKeyboard_18.png
new file mode 100644
index 00000000000..31c10b444b9
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_18.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_19.png b/developer/docs/help/images/simpleTouchKeyboard_19.png
new file mode 100644
index 00000000000..b547473cf33
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_19.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_2.png b/developer/docs/help/images/simpleTouchKeyboard_2.png
new file mode 100644
index 00000000000..763ca83a0c7
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_2.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_20.png b/developer/docs/help/images/simpleTouchKeyboard_20.png
new file mode 100644
index 00000000000..82d06a8cc05
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_20.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_3.png b/developer/docs/help/images/simpleTouchKeyboard_3.png
new file mode 100644
index 00000000000..6779461e05d
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_3.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_36.png b/developer/docs/help/images/simpleTouchKeyboard_36.png
new file mode 100644
index 00000000000..ae690484cec
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_36.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_4.png b/developer/docs/help/images/simpleTouchKeyboard_4.png
new file mode 100644
index 00000000000..023bd944e55
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_4.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_5.png b/developer/docs/help/images/simpleTouchKeyboard_5.png
new file mode 100644
index 00000000000..7038dc0515c
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_5.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_6.png b/developer/docs/help/images/simpleTouchKeyboard_6.png
new file mode 100644
index 00000000000..85b461e8c4b
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_6.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_7.png b/developer/docs/help/images/simpleTouchKeyboard_7.png
new file mode 100644
index 00000000000..6b2d2c06750
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_7.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_8.png b/developer/docs/help/images/simpleTouchKeyboard_8.png
new file mode 100644
index 00000000000..a161a657c54
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_8.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_9.png b/developer/docs/help/images/simpleTouchKeyboard_9.png
new file mode 100644
index 00000000000..b246c832199
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_9.png differ
diff --git a/developer/docs/help/images/testing/chrome1.png b/developer/docs/help/images/testing/chrome1.png
new file mode 100644
index 00000000000..3adbe9cde85
Binary files /dev/null and b/developer/docs/help/images/testing/chrome1.png differ
diff --git a/developer/docs/help/images/testing/chrome2.png b/developer/docs/help/images/testing/chrome2.png
new file mode 100644
index 00000000000..713019ce2c2
Binary files /dev/null and b/developer/docs/help/images/testing/chrome2.png differ
diff --git a/developer/docs/help/images/testing/chrome3.png b/developer/docs/help/images/testing/chrome3.png
new file mode 100644
index 00000000000..76df121c401
Binary files /dev/null and b/developer/docs/help/images/testing/chrome3.png differ
diff --git a/developer/docs/help/images/testing/chrome4.png b/developer/docs/help/images/testing/chrome4.png
new file mode 100644
index 00000000000..8e3d70fc50d
Binary files /dev/null and b/developer/docs/help/images/testing/chrome4.png differ
diff --git a/developer/docs/help/images/testing/frame/android-debug-host.png b/developer/docs/help/images/testing/frame/android-debug-host.png
new file mode 100644
index 00000000000..2783af508c9
Binary files /dev/null and b/developer/docs/help/images/testing/frame/android-debug-host.png differ
diff --git a/developer/docs/help/images/testing/frame/android-debug-keyboard.png b/developer/docs/help/images/testing/frame/android-debug-keyboard.png
new file mode 100644
index 00000000000..a112a881aa9
Binary files /dev/null and b/developer/docs/help/images/testing/frame/android-debug-keyboard.png differ
diff --git a/developer/docs/help/images/testing/frame/android-enter-debug-host.png b/developer/docs/help/images/testing/frame/android-enter-debug-host.png
new file mode 100644
index 00000000000..13070f46800
Binary files /dev/null and b/developer/docs/help/images/testing/frame/android-enter-debug-host.png differ
diff --git a/developer/docs/help/images/testing/frame/installing-native-keyboard-1.png b/developer/docs/help/images/testing/frame/installing-native-keyboard-1.png
new file mode 100644
index 00000000000..b115fdbec82
Binary files /dev/null and b/developer/docs/help/images/testing/frame/installing-native-keyboard-1.png differ
diff --git a/developer/docs/help/images/testing/frame/installing-native-keyboard-2.png b/developer/docs/help/images/testing/frame/installing-native-keyboard-2.png
new file mode 100644
index 00000000000..448c2d20bef
Binary files /dev/null and b/developer/docs/help/images/testing/frame/installing-native-keyboard-2.png differ
diff --git a/developer/docs/help/images/testing/startdebugging-kd10.png b/developer/docs/help/images/testing/startdebugging-kd10.png
new file mode 100644
index 00000000000..197d0b9c7b8
Binary files /dev/null and b/developer/docs/help/images/testing/startdebugging-kd10.png differ
diff --git a/developer/docs/help/images/testing/touch-editor-build-800wi.png b/developer/docs/help/images/testing/touch-editor-build-800wi.png
new file mode 100644
index 00000000000..99a9c4c690a
Binary files /dev/null and b/developer/docs/help/images/testing/touch-editor-build-800wi.png differ
diff --git a/developer/docs/help/images/testing/touch-editor-build.png b/developer/docs/help/images/testing/touch-editor-build.png
new file mode 100644
index 00000000000..26f1f6ec759
Binary files /dev/null and b/developer/docs/help/images/testing/touch-editor-build.png differ
diff --git a/developer/docs/help/images/testing/touch-editor-khmer-800wi.png b/developer/docs/help/images/testing/touch-editor-khmer-800wi.png
new file mode 100644
index 00000000000..26ad7b4936f
Binary files /dev/null and b/developer/docs/help/images/testing/touch-editor-khmer-800wi.png differ
diff --git a/developer/docs/help/images/testing/touch-editor-khmer.png b/developer/docs/help/images/testing/touch-editor-khmer.png
new file mode 100644
index 00000000000..5658e0f9f73
Binary files /dev/null and b/developer/docs/help/images/testing/touch-editor-khmer.png differ
diff --git a/developer/docs/help/images/touch_amharic_keyboard.png b/developer/docs/help/images/touch_amharic_keyboard.png
new file mode 100644
index 00000000000..57a9693a1bc
Binary files /dev/null and b/developer/docs/help/images/touch_amharic_keyboard.png differ
diff --git a/developer/docs/help/images/touch_amharic_keyboard_2.png b/developer/docs/help/images/touch_amharic_keyboard_2.png
new file mode 100644
index 00000000000..a0e2fcb3764
Binary files /dev/null and b/developer/docs/help/images/touch_amharic_keyboard_2.png differ
diff --git a/developer/docs/help/images/touch_amharic_keyboard_3.png b/developer/docs/help/images/touch_amharic_keyboard_3.png
new file mode 100644
index 00000000000..bf6bc5a4091
Binary files /dev/null and b/developer/docs/help/images/touch_amharic_keyboard_3.png differ
diff --git a/developer/docs/help/images/touch_amharic_keyboard_4.png b/developer/docs/help/images/touch_amharic_keyboard_4.png
new file mode 100644
index 00000000000..689ab500a64
Binary files /dev/null and b/developer/docs/help/images/touch_amharic_keyboard_4.png differ
diff --git a/developer/docs/help/images/touch_amharic_keyboard_5.png b/developer/docs/help/images/touch_amharic_keyboard_5.png
new file mode 100644
index 00000000000..1d06fc3ef3f
Binary files /dev/null and b/developer/docs/help/images/touch_amharic_keyboard_5.png differ
diff --git a/developer/docs/help/images/touch_amharic_keyboard_6.png b/developer/docs/help/images/touch_amharic_keyboard_6.png
new file mode 100644
index 00000000000..a9979426b6a
Binary files /dev/null and b/developer/docs/help/images/touch_amharic_keyboard_6.png differ
diff --git a/developer/docs/help/images/touch_amharic_keyboard_7.png b/developer/docs/help/images/touch_amharic_keyboard_7.png
new file mode 100644
index 00000000000..454a73bf200
Binary files /dev/null and b/developer/docs/help/images/touch_amharic_keyboard_7.png differ
diff --git a/developer/docs/help/images/touch_amharic_keyboard_8.png b/developer/docs/help/images/touch_amharic_keyboard_8.png
new file mode 100644
index 00000000000..2c7e5a297cf
Binary files /dev/null and b/developer/docs/help/images/touch_amharic_keyboard_8.png differ
diff --git a/developer/docs/help/images/touch_amharic_keyboard_9.png b/developer/docs/help/images/touch_amharic_keyboard_9.png
new file mode 100644
index 00000000000..205e428d73f
Binary files /dev/null and b/developer/docs/help/images/touch_amharic_keyboard_9.png differ
diff --git a/developer/docs/help/images/tutorial_distribute_keyboard_3.png b/developer/docs/help/images/tutorial_distribute_keyboard_3.png
new file mode 100644
index 00000000000..33e7cdf336a
Binary files /dev/null and b/developer/docs/help/images/tutorial_distribute_keyboard_3.png differ
diff --git a/developer/docs/help/images/tutorial_distribute_keyboard_3_files.png b/developer/docs/help/images/tutorial_distribute_keyboard_3_files.png
new file mode 100644
index 00000000000..561931522f3
Binary files /dev/null and b/developer/docs/help/images/tutorial_distribute_keyboard_3_files.png differ
diff --git a/developer/docs/help/images/tutorial_distribute_keyboard_compile.png b/developer/docs/help/images/tutorial_distribute_keyboard_compile.png
new file mode 100644
index 00000000000..865fa186e55
Binary files /dev/null and b/developer/docs/help/images/tutorial_distribute_keyboard_compile.png differ
diff --git a/developer/docs/help/images/tutorial_distribute_keyboard_details.png b/developer/docs/help/images/tutorial_distribute_keyboard_details.png
new file mode 100644
index 00000000000..08dd9ec0123
Binary files /dev/null and b/developer/docs/help/images/tutorial_distribute_keyboard_details.png differ
diff --git a/developer/docs/help/images/tutorial_distribute_keyboard_shortcuts.png b/developer/docs/help/images/tutorial_distribute_keyboard_shortcuts.png
new file mode 100644
index 00000000000..8084a3797a4
Binary files /dev/null and b/developer/docs/help/images/tutorial_distribute_keyboard_shortcuts.png differ
diff --git a/developer/docs/help/images/tutorial_keyboard_qfrench.gif b/developer/docs/help/images/tutorial_keyboard_qfrench.gif
new file mode 100644
index 00000000000..99e274b4548
Binary files /dev/null and b/developer/docs/help/images/tutorial_keyboard_qfrench.gif differ
diff --git a/developer/docs/help/images/tutorial_package_includesdocs.gif b/developer/docs/help/images/tutorial_package_includesdocs.gif
new file mode 100644
index 00000000000..6eebbb9fc0c
Binary files /dev/null and b/developer/docs/help/images/tutorial_package_includesdocs.gif differ
diff --git a/developer/docs/help/images/tutorial_package_includesfonts.gif b/developer/docs/help/images/tutorial_package_includesfonts.gif
new file mode 100644
index 00000000000..cbeca7bd533
Binary files /dev/null and b/developer/docs/help/images/tutorial_package_includesfonts.gif differ
diff --git a/developer/docs/help/images/tutorial_package_includesosk.gif b/developer/docs/help/images/tutorial_package_includesosk.gif
new file mode 100644
index 00000000000..5db43bf1b95
Binary files /dev/null and b/developer/docs/help/images/tutorial_package_includesosk.gif differ
diff --git a/developer/docs/help/images/tutorial_package_includeswelcome.gif b/developer/docs/help/images/tutorial_package_includeswelcome.gif
new file mode 100644
index 00000000000..9313bd04812
Binary files /dev/null and b/developer/docs/help/images/tutorial_package_includeswelcome.gif differ
diff --git a/developer/docs/help/images/ui/BuildTab.png b/developer/docs/help/images/ui/BuildTab.png
new file mode 100644
index 00000000000..5fe027dcc69
Binary files /dev/null and b/developer/docs/help/images/ui/BuildTab.png differ
diff --git a/developer/docs/help/images/ui/Debug_Menu.png b/developer/docs/help/images/ui/Debug_Menu.png
new file mode 100644
index 00000000000..61e749cfa6d
Binary files /dev/null and b/developer/docs/help/images/ui/Debug_Menu.png differ
diff --git a/developer/docs/help/images/ui/Debug_Toolbar.png b/developer/docs/help/images/ui/Debug_Toolbar.png
new file mode 100644
index 00000000000..0fc598f2f8a
Binary files /dev/null and b/developer/docs/help/images/ui/Debug_Toolbar.png differ
diff --git a/developer/docs/help/images/ui/LongPress_Flicks_Multitaps.png b/developer/docs/help/images/ui/LongPress_Flicks_Multitaps.png
new file mode 100644
index 00000000000..ba398273d1e
Binary files /dev/null and b/developer/docs/help/images/ui/LongPress_Flicks_Multitaps.png differ
diff --git a/developer/docs/help/images/ui/NGROK_Phone.png b/developer/docs/help/images/ui/NGROK_Phone.png
new file mode 100644
index 00000000000..b33ef1e3166
Binary files /dev/null and b/developer/docs/help/images/ui/NGROK_Phone.png differ
diff --git a/developer/docs/help/images/ui/OnScreenKeyboard.png b/developer/docs/help/images/ui/OnScreenKeyboard.png
new file mode 100644
index 00000000000..cb827878b01
Binary files /dev/null and b/developer/docs/help/images/ui/OnScreenKeyboard.png differ
diff --git a/developer/docs/help/images/ui/TouchLayout_Design.png b/developer/docs/help/images/ui/TouchLayout_Design.png
new file mode 100644
index 00000000000..3e073e812ff
Binary files /dev/null and b/developer/docs/help/images/ui/TouchLayout_Design.png differ
diff --git a/developer/docs/help/images/ui/TouchLayout_Design_2.png b/developer/docs/help/images/ui/TouchLayout_Design_2.png
new file mode 100644
index 00000000000..6bae06255f9
Binary files /dev/null and b/developer/docs/help/images/ui/TouchLayout_Design_2.png differ
diff --git a/developer/docs/help/images/ui/frmAboutTike.png b/developer/docs/help/images/ui/frmAboutTike.png
new file mode 100644
index 00000000000..240f1ce87b0
Binary files /dev/null and b/developer/docs/help/images/ui/frmAboutTike.png differ
diff --git a/developer/docs/help/images/ui/frmCharacterMapFilter.png b/developer/docs/help/images/ui/frmCharacterMapFilter.png
new file mode 100644
index 00000000000..52efac91d70
Binary files /dev/null and b/developer/docs/help/images/ui/frmCharacterMapFilter.png differ
diff --git a/developer/docs/help/images/ui/frmCharacterMapNew.png b/developer/docs/help/images/ui/frmCharacterMapNew.png
new file mode 100644
index 00000000000..4a933022f75
Binary files /dev/null and b/developer/docs/help/images/ui/frmCharacterMapNew.png differ
diff --git a/developer/docs/help/images/ui/frmDebug.png b/developer/docs/help/images/ui/frmDebug.png
new file mode 100644
index 00000000000..1aa67a4036a
Binary files /dev/null and b/developer/docs/help/images/ui/frmDebug.png differ
diff --git a/developer/docs/help/images/ui/frmEditLanguageExample.png b/developer/docs/help/images/ui/frmEditLanguageExample.png
new file mode 100644
index 00000000000..352da90d8a9
Binary files /dev/null and b/developer/docs/help/images/ui/frmEditLanguageExample.png differ
diff --git a/developer/docs/help/images/ui/frmKeyTest.png b/developer/docs/help/images/ui/frmKeyTest.png
new file mode 100644
index 00000000000..583bc16793c
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeyTest.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_Debug_CallStack.png b/developer/docs/help/images/ui/frmKeymanWizard_Debug_CallStack.png
new file mode 100644
index 00000000000..aff2c122322
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_Debug_CallStack.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_Debug_Deadkeys.png b/developer/docs/help/images/ui/frmKeymanWizard_Debug_Deadkeys.png
new file mode 100644
index 00000000000..4b48d4f61f3
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_Debug_Deadkeys.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_Debug_Elements.png b/developer/docs/help/images/ui/frmKeymanWizard_Debug_Elements.png
new file mode 100644
index 00000000000..fc78a911958
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_Debug_Elements.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_Debug_RegressionTest.png b/developer/docs/help/images/ui/frmKeymanWizard_Debug_RegressionTest.png
new file mode 100644
index 00000000000..bebe3ac569c
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_Debug_RegressionTest.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_Debug_State.png b/developer/docs/help/images/ui/frmKeymanWizard_Debug_State.png
new file mode 100644
index 00000000000..d14ed69a4f3
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_Debug_State.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_Details.png b/developer/docs/help/images/ui/frmKeymanWizard_Details.png
new file mode 100644
index 00000000000..d21db5f87f6
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_Details.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_Icon.png b/developer/docs/help/images/ui/frmKeymanWizard_Icon.png
new file mode 100644
index 00000000000..92a621ac93f
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_Icon.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_Layout_Code.png b/developer/docs/help/images/ui/frmKeymanWizard_Layout_Code.png
new file mode 100644
index 00000000000..2fbe3991e59
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_Layout_Code.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_Layout_Design.png b/developer/docs/help/images/ui/frmKeymanWizard_Layout_Design.png
new file mode 100644
index 00000000000..5d3b570555b
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_Layout_Design.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_New.png b/developer/docs/help/images/ui/frmKeymanWizard_New.png
new file mode 100644
index 00000000000..7fdb5ac14a0
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_New.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_TouchLayout_Code.png b/developer/docs/help/images/ui/frmKeymanWizard_TouchLayout_Code.png
new file mode 100644
index 00000000000..b20f0bd2852
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_TouchLayout_Code.png differ
diff --git a/developer/docs/help/images/ui/frmKeyman_Developer_Server_Options.png b/developer/docs/help/images/ui/frmKeyman_Developer_Server_Options.png
new file mode 100644
index 00000000000..6f0c8313085
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeyman_Developer_Server_Options.png differ
diff --git a/developer/docs/help/images/ui/frmLDMLEditor.png b/developer/docs/help/images/ui/frmLDMLEditor.png
new file mode 100644
index 00000000000..c3f887ab641
Binary files /dev/null and b/developer/docs/help/images/ui/frmLDMLEditor.png differ
diff --git a/developer/docs/help/images/ui/frmList_Local_URLs.png b/developer/docs/help/images/ui/frmList_Local_URLs.png
new file mode 100644
index 00000000000..18917dfc8f8
Binary files /dev/null and b/developer/docs/help/images/ui/frmList_Local_URLs.png differ
diff --git a/developer/docs/help/images/ui/frmMessages.png b/developer/docs/help/images/ui/frmMessages.png
new file mode 100644
index 00000000000..377e9290344
Binary files /dev/null and b/developer/docs/help/images/ui/frmMessages.png differ
diff --git a/developer/docs/help/images/ui/frmModelEditor_Build.png b/developer/docs/help/images/ui/frmModelEditor_Build.png
new file mode 100644
index 00000000000..b437544518f
Binary files /dev/null and b/developer/docs/help/images/ui/frmModelEditor_Build.png differ
diff --git a/developer/docs/help/images/ui/frmModelEditor_Details.png b/developer/docs/help/images/ui/frmModelEditor_Details.png
new file mode 100644
index 00000000000..a9eb7163c8c
Binary files /dev/null and b/developer/docs/help/images/ui/frmModelEditor_Details.png differ
diff --git a/developer/docs/help/images/ui/frmModelEditor_Source.png b/developer/docs/help/images/ui/frmModelEditor_Source.png
new file mode 100644
index 00000000000..d887196e510
Binary files /dev/null and b/developer/docs/help/images/ui/frmModelEditor_Source.png differ
diff --git a/developer/docs/help/images/ui/frmModelEditor_Wordlist_Code.png b/developer/docs/help/images/ui/frmModelEditor_Wordlist_Code.png
new file mode 100644
index 00000000000..f853f76fc36
Binary files /dev/null and b/developer/docs/help/images/ui/frmModelEditor_Wordlist_Code.png differ
diff --git a/developer/docs/help/images/ui/frmModelEditor_Wordlist_Design.png b/developer/docs/help/images/ui/frmModelEditor_Wordlist_Design.png
new file mode 100644
index 00000000000..d0770cd804e
Binary files /dev/null and b/developer/docs/help/images/ui/frmModelEditor_Wordlist_Design.png differ
diff --git a/developer/docs/help/images/ui/frmModelsCompile.png b/developer/docs/help/images/ui/frmModelsCompile.png
new file mode 100644
index 00000000000..d02fdf1eba9
Binary files /dev/null and b/developer/docs/help/images/ui/frmModelsCompile.png differ
diff --git a/developer/docs/help/images/ui/frmNGROK_Config.png b/developer/docs/help/images/ui/frmNGROK_Config.png
new file mode 100644
index 00000000000..ffbbecef230
Binary files /dev/null and b/developer/docs/help/images/ui/frmNGROK_Config.png differ
diff --git a/developer/docs/help/images/ui/frmNGROK_Server_1.png b/developer/docs/help/images/ui/frmNGROK_Server_1.png
new file mode 100644
index 00000000000..e617f9313dd
Binary files /dev/null and b/developer/docs/help/images/ui/frmNGROK_Server_1.png differ
diff --git a/developer/docs/help/images/ui/frmNGROK_Server_2.png b/developer/docs/help/images/ui/frmNGROK_Server_2.png
new file mode 100644
index 00000000000..b8b3e571d9d
Binary files /dev/null and b/developer/docs/help/images/ui/frmNGROK_Server_2.png differ
diff --git a/developer/docs/help/images/ui/frmNew.png b/developer/docs/help/images/ui/frmNew.png
new file mode 100644
index 00000000000..6748baf7d82
Binary files /dev/null and b/developer/docs/help/images/ui/frmNew.png differ
diff --git a/developer/docs/help/images/ui/frmNewLDMLProjectParameters.png b/developer/docs/help/images/ui/frmNewLDMLProjectParameters.png
new file mode 100644
index 00000000000..6002ea9943d
Binary files /dev/null and b/developer/docs/help/images/ui/frmNewLDMLProjectParameters.png differ
diff --git a/developer/docs/help/images/ui/frmNewLMProject.png b/developer/docs/help/images/ui/frmNewLMProject.png
new file mode 100644
index 00000000000..6e1b096f51d
Binary files /dev/null and b/developer/docs/help/images/ui/frmNewLMProject.png differ
diff --git a/developer/docs/help/images/ui/frmNewLMProjectParameters.png b/developer/docs/help/images/ui/frmNewLMProjectParameters.png
new file mode 100644
index 00000000000..d58b569064c
Binary files /dev/null and b/developer/docs/help/images/ui/frmNewLMProjectParameters.png differ
diff --git a/developer/docs/help/images/ui/frmNewLMProjectSelectLanguage.png b/developer/docs/help/images/ui/frmNewLMProjectSelectLanguage.png
new file mode 100644
index 00000000000..8767a2d2670
Binary files /dev/null and b/developer/docs/help/images/ui/frmNewLMProjectSelectLanguage.png differ
diff --git a/developer/docs/help/images/ui/frmNewProject.png b/developer/docs/help/images/ui/frmNewProject.png
new file mode 100644
index 00000000000..8fc13cd763f
Binary files /dev/null and b/developer/docs/help/images/ui/frmNewProject.png differ
diff --git a/developer/docs/help/images/ui/frmNewProjectParameters.png b/developer/docs/help/images/ui/frmNewProjectParameters.png
new file mode 100644
index 00000000000..df93ecac2f2
Binary files /dev/null and b/developer/docs/help/images/ui/frmNewProjectParameters.png differ
diff --git a/developer/docs/help/images/ui/frmOptions_CharacterMap.png b/developer/docs/help/images/ui/frmOptions_CharacterMap.png
new file mode 100644
index 00000000000..75c8fc38433
Binary files /dev/null and b/developer/docs/help/images/ui/frmOptions_CharacterMap.png differ
diff --git a/developer/docs/help/images/ui/frmOptions_Debugger.png b/developer/docs/help/images/ui/frmOptions_Debugger.png
new file mode 100644
index 00000000000..adb2f56c20f
Binary files /dev/null and b/developer/docs/help/images/ui/frmOptions_Debugger.png differ
diff --git a/developer/docs/help/images/ui/frmOptions_Editor.png b/developer/docs/help/images/ui/frmOptions_Editor.png
new file mode 100644
index 00000000000..ae8d858d30c
Binary files /dev/null and b/developer/docs/help/images/ui/frmOptions_Editor.png differ
diff --git a/developer/docs/help/images/ui/frmOptions_General.png b/developer/docs/help/images/ui/frmOptions_General.png
new file mode 100644
index 00000000000..514c04a5f71
Binary files /dev/null and b/developer/docs/help/images/ui/frmOptions_General.png differ
diff --git a/developer/docs/help/images/ui/frmOptions_Server.png b/developer/docs/help/images/ui/frmOptions_Server.png
new file mode 100644
index 00000000000..47b4beb8193
Binary files /dev/null and b/developer/docs/help/images/ui/frmOptions_Server.png differ
diff --git a/developer/docs/help/images/ui/frmPackageEditor_Build.png b/developer/docs/help/images/ui/frmPackageEditor_Build.png
new file mode 100644
index 00000000000..953f1b60662
Binary files /dev/null and b/developer/docs/help/images/ui/frmPackageEditor_Build.png differ
diff --git a/developer/docs/help/images/ui/frmPackageEditor_Details.png b/developer/docs/help/images/ui/frmPackageEditor_Details.png
new file mode 100644
index 00000000000..be071b7e294
Binary files /dev/null and b/developer/docs/help/images/ui/frmPackageEditor_Details.png differ
diff --git a/developer/docs/help/images/ui/frmPackageEditor_EditExample.png b/developer/docs/help/images/ui/frmPackageEditor_EditExample.png
new file mode 100644
index 00000000000..96c70237a9a
Binary files /dev/null and b/developer/docs/help/images/ui/frmPackageEditor_EditExample.png differ
diff --git a/developer/docs/help/images/ui/frmPackageEditor_EditRelatedPackage.png b/developer/docs/help/images/ui/frmPackageEditor_EditRelatedPackage.png
new file mode 100644
index 00000000000..977fb822432
Binary files /dev/null and b/developer/docs/help/images/ui/frmPackageEditor_EditRelatedPackage.png differ
diff --git a/developer/docs/help/images/ui/frmPackageEditor_Files.png b/developer/docs/help/images/ui/frmPackageEditor_Files.png
new file mode 100644
index 00000000000..6b9b6a4cef4
Binary files /dev/null and b/developer/docs/help/images/ui/frmPackageEditor_Files.png differ
diff --git a/developer/docs/help/images/ui/frmPackageEditor_Keyboards.png b/developer/docs/help/images/ui/frmPackageEditor_Keyboards.png
new file mode 100644
index 00000000000..fc252413d57
Binary files /dev/null and b/developer/docs/help/images/ui/frmPackageEditor_Keyboards.png differ
diff --git a/developer/docs/help/images/ui/frmPackageEditor_SelectWebFonts.png b/developer/docs/help/images/ui/frmPackageEditor_SelectWebFonts.png
new file mode 100644
index 00000000000..64d284dc458
Binary files /dev/null and b/developer/docs/help/images/ui/frmPackageEditor_SelectWebFonts.png differ
diff --git a/developer/docs/help/images/ui/frmPackageEditor_Select_BCP_47_Tag.png b/developer/docs/help/images/ui/frmPackageEditor_Select_BCP_47_Tag.png
new file mode 100644
index 00000000000..5895b452901
Binary files /dev/null and b/developer/docs/help/images/ui/frmPackageEditor_Select_BCP_47_Tag.png differ
diff --git a/developer/docs/help/images/ui/frmPackageEditor_Shortcuts.png b/developer/docs/help/images/ui/frmPackageEditor_Shortcuts.png
new file mode 100644
index 00000000000..e9fe8c2fc45
Binary files /dev/null and b/developer/docs/help/images/ui/frmPackageEditor_Shortcuts.png differ
diff --git a/developer/docs/help/images/ui/frmPackageEditor_Source.png b/developer/docs/help/images/ui/frmPackageEditor_Source.png
new file mode 100644
index 00000000000..3ab4d8406f6
Binary files /dev/null and b/developer/docs/help/images/ui/frmPackageEditor_Source.png differ
diff --git a/developer/docs/help/images/ui/frmProject_Distribution.png b/developer/docs/help/images/ui/frmProject_Distribution.png
new file mode 100644
index 00000000000..5f05db0747e
Binary files /dev/null and b/developer/docs/help/images/ui/frmProject_Distribution.png differ
diff --git a/developer/docs/help/images/ui/frmProject_Keyboards.png b/developer/docs/help/images/ui/frmProject_Keyboards.png
new file mode 100644
index 00000000000..7d9e6ef36e0
Binary files /dev/null and b/developer/docs/help/images/ui/frmProject_Keyboards.png differ
diff --git a/developer/docs/help/images/ui/frmProject_Packaging.png b/developer/docs/help/images/ui/frmProject_Packaging.png
new file mode 100644
index 00000000000..6b7db832371
Binary files /dev/null and b/developer/docs/help/images/ui/frmProject_Packaging.png differ
diff --git a/developer/docs/help/images/ui/frmProject_Welcome.png b/developer/docs/help/images/ui/frmProject_Welcome.png
new file mode 100644
index 00000000000..7b899c4fe6e
Binary files /dev/null and b/developer/docs/help/images/ui/frmProject_Welcome.png differ
diff --git a/developer/docs/help/images/ui/obj.gif b/developer/docs/help/images/ui/obj.gif
new file mode 100644
index 00000000000..455e0b330a9
Binary files /dev/null and b/developer/docs/help/images/ui/obj.gif differ
diff --git a/developer/docs/help/index.md b/developer/docs/help/index.md
new file mode 100644
index 00000000000..9e9c250abb3
--- /dev/null
+++ b/developer/docs/help/index.md
@@ -0,0 +1,38 @@
+---
+title: Keyman Developer 18.0 User Guide
+---
+
+Need help using Keyman Developer to create your keyboard layouts? You'll
+find everything you need here, including product documentation, guides
+and tutorials, and full reference information.
+
+## Guides and Tutorials
+- [What is Keyman Developer?](guides/intro)
+- [What's new in 18.0](whats-new)
+- [Developing Keyman keyboard layouts](guides/develop)
+- [Testing Keyman keyboards](guides/test)
+- [Distributing Keyman keyboards](guides/distribute)
+- [What is a lexical model?](guides/lexical-models)
+- [Developing a lexical model](guides/lexical-models/tutorial)
+
+## Reference
+- [Keyman keyboard language reference](../language/reference)
+- [Keyman keyboard language guide](../language/guide)
+- [Customising Keyman Engine for Desktop](../engine/desktop)
+- [Error codes, File types and Tools](reference/)
+- [Keyman Developer user interface](context/)
+- [Additional Notes](main/)
+- [Version History](../version-history/)
+
+## Ask for Help
+- [Ask other users in the SIL Keyman Community](https://community.software.sil.org/c/keyman)
+
+## Other Resources
+
+- [Keyboard Quality White Paper](/developer/whitepaper1.1.pdf)
+- [Knowledge Base](/kb)
+- [Keyman Desktop Help](/products/desktop)
+- [Keyman Engine for Desktop](/developer/engine/desktop/current-version/)
+- [Keyman Engine for Web](/developer/engine/web/)
+- [Keyman Engine for iPhone and iPad](/developer/engine/iphone-and-ipad/current-version/)
+- [Keyman Engine for Android](/developer/engine/android/current-version/)
\ No newline at end of file
diff --git a/developer/docs/help/main/credits.md b/developer/docs/help/main/credits.md
new file mode 100644
index 00000000000..f2c8a027c5b
--- /dev/null
+++ b/developer/docs/help/main/credits.md
@@ -0,0 +1,7 @@
+---
+title: Additional Credits
+---
+
+## Code2000, Code2001 and Code2002 fonts
+
+© 2006 James Kass.
diff --git a/developer/docs/help/main/index.md b/developer/docs/help/main/index.md
new file mode 100644
index 00000000000..6e309aa46e0
--- /dev/null
+++ b/developer/docs/help/main/index.md
@@ -0,0 +1,9 @@
+---
+title: Keyman Developer Additional Notes
+---
+
+[Keyman Developer Version History](../../version-history)
+
+[Keyman® Developer End User Licence and Services Agreement](license)
+
+[Additional Credits](credits)
diff --git a/developer/docs/help/main/license.md b/developer/docs/help/main/license.md
new file mode 100644
index 00000000000..73b788f9000
--- /dev/null
+++ b/developer/docs/help/main/license.md
@@ -0,0 +1,26 @@
+---
+title: Keyman® Developer License
+---
+
+MIT License
+
+Copyright (c) 1994-2018 SIL International
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/developer/docs/help/reference/api/index.md b/developer/docs/help/reference/api/index.md
new file mode 100644
index 00000000000..caac7457fed
--- /dev/null
+++ b/developer/docs/help/reference/api/index.md
@@ -0,0 +1,18 @@
+
+
+[Home](./index.md)
+
+## API Reference
+
+## Packages
+
+| Package | Description |
+| --- | --- |
+| [@keymanapp/kmc-analyze](./kmc-analyze.md) | kmc-analyze - keyboard analysis classes, including tools for &displayMap
. |
+| [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) | |
+| [@keymanapp/kmc-kmn](./kmc-kmn.md) | kmc-kmn - Keyman keyboard compiler |
+| [@keymanapp/kmc-ldml](./kmc-ldml.md) | |
+| [@keymanapp/kmc-model](./kmc-model.md) | |
+| [@keymanapp/kmc-model-info](./kmc-model-info.md) | |
+| [@keymanapp/kmc-package](./kmc-package.md) | |
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse._constructor_.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse._constructor_.md
new file mode 100644
index 00000000000..b2270b6c17b
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse._constructor_.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskCharacterUse](./kmc-analyze.analyzeoskcharacteruse.md) > [(constructor)](./kmc-analyze.analyzeoskcharacteruse._constructor_.md)
+
+## AnalyzeOskCharacterUse.(constructor)
+
+Constructs a new instance of the `AnalyzeOskCharacterUse` class
+
+**Signature:**
+
+```typescript
+constructor(callbacks: CompilerCallbacks, options?: AnalyzeOskCharacterUseOptions);
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| callbacks | CompilerCallbacks | |
+| options | [AnalyzeOskCharacterUseOptions](./kmc-analyze.analyzeoskcharacteruseoptions.md) | _(Optional)_ |
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.analyze.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.analyze.md
new file mode 100644
index 00000000000..b4e8732921d
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.analyze.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskCharacterUse](./kmc-analyze.analyzeoskcharacteruse.md) > [analyze](./kmc-analyze.analyzeoskcharacteruse.analyze.md)
+
+## AnalyzeOskCharacterUse.analyze() method
+
+Analyzes a single source file for Unicode character usage. Can parse .kmn, .kvks, .keyman-touch-layout file formats. Can be called multiple times to collect results from more than one file. Use [AnalyzeOskCharacterUse.getStrings()](./kmc-analyze.analyzeoskcharacteruse.getstrings.md) to retrieve results.
+
+Note: `analyze()` collects key cap data, so calling this for a .kmn file will retrieve the key caps from the .kvks and .keyman-touch-layout files that it references, rather than key cap data from the .kmn file itself.
+
+**Signature:**
+
+```typescript
+analyze(file: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| file | string | relative or absolute path to a Keyman source file |
+
+**Returns:**
+
+Promise<boolean>
+
+true if the file is successfully loaded and parsed
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.clear.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.clear.md
new file mode 100644
index 00000000000..0c8cee7b99f
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.clear.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskCharacterUse](./kmc-analyze.analyzeoskcharacteruse.md) > [clear](./kmc-analyze.analyzeoskcharacteruse.clear.md)
+
+## AnalyzeOskCharacterUse.clear() method
+
+Clears analysis data collected from previous calls to [AnalyzeOskCharacterUse.analyze()](./kmc-analyze.analyzeoskcharacteruse.analyze.md)
+
+**Signature:**
+
+```typescript
+clear(): void;
+```
+**Returns:**
+
+void
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.getstrings.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.getstrings.md
new file mode 100644
index 00000000000..c01f1a76d9f
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.getstrings.md
@@ -0,0 +1,34 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskCharacterUse](./kmc-analyze.analyzeoskcharacteruse.md) > [getStrings](./kmc-analyze.analyzeoskcharacteruse.getstrings.md)
+
+## AnalyzeOskCharacterUse.getStrings() method
+
+Returns the collected results from earlier calls to [AnalyzeOskCharacterUse.analyze()](./kmc-analyze.analyzeoskcharacteruse.analyze.md). This generates a mapping from a key cap (one or more characters) to a PUA code, for use with `&displayMap`.
+
+Three output formats are supported:
+
+- .txt: tab-separated string format, with three columns: PUA, Key Cap, and plain string. The PUA and Key Cap columns are formatted as Unicode Scalar Values, e.g. U+0061, and the plain string is the original key cap string.
+
+- .md: formatted for documentation purposes. Generates a Markdown table (GFM) with PUA, Key Cap, and plain string. The PUA and Key Cap columns are formatted as Unicode Scalar Values, e.g. U+0061, and the plain string is the original key cap string.
+
+- .json: returns the final aggregated data as an array of strings, which can be joined to form a JSON blob of an object with a single member, `map`, which is an array of objects.
+
+**Signature:**
+
+```typescript
+getStrings(format?: '.txt' | '.md' | '.json'): string[];
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| format | '.txt' \| '.md' \| '.json' | _(Optional)_ file format to return - can be '.txt', '.md', or '.json' |
+
+**Returns:**
+
+string\[\]
+
+an array of strings, formatted according to the `format` parameter.
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.md
new file mode 100644
index 00000000000..72302bc73c4
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskCharacterUse](./kmc-analyze.analyzeoskcharacteruse.md)
+
+## AnalyzeOskCharacterUse class
+
+Analyze the characters used in On Screen Keyboard files (.kvks, .keyman-touch-layout) for use with `&displayMap`.
+
+**Signature:**
+
+```typescript
+export declare class AnalyzeOskCharacterUse
+```
+
+## Constructors
+
+| Constructor | Modifiers | Description |
+| --- | --- | --- |
+| [(constructor)(callbacks, options)](./kmc-analyze.analyzeoskcharacteruse._constructor_.md) | | Constructs a new instance of the AnalyzeOskCharacterUse
class |
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [analyze(file)](./kmc-analyze.analyzeoskcharacteruse.analyze.md) | | Analyzes a single source file for Unicode character usage. Can parse .kmn, .kvks, .keyman-touch-layout file formats. Can be called multiple times to collect results from more than one file. Use [AnalyzeOskCharacterUse.getStrings()](./kmc-analyze.analyzeoskcharacteruse.getstrings.md) to retrieve results.
Note: analyze()
collects key cap data, so calling this for a .kmn file will retrieve the key caps from the .kvks and .keyman-touch-layout files that it references, rather than key cap data from the .kmn file itself.
|
+| [clear()](./kmc-analyze.analyzeoskcharacteruse.clear.md) | | Clears analysis data collected from previous calls to [AnalyzeOskCharacterUse.analyze()](./kmc-analyze.analyzeoskcharacteruse.analyze.md) |
+| [getStrings(format)](./kmc-analyze.analyzeoskcharacteruse.getstrings.md) | | Returns the collected results from earlier calls to [AnalyzeOskCharacterUse.analyze()](./kmc-analyze.analyzeoskcharacteruse.analyze.md). This generates a mapping from a key cap (one or more characters) to a PUA code, for use with &displayMap
.
Three output formats are supported:
- .txt: tab-separated string format, with three columns: PUA, Key Cap, and plain string. The PUA and Key Cap columns are formatted as Unicode Scalar Values, e.g. U+0061, and the plain string is the original key cap string.
- .md: formatted for documentation purposes. Generates a Markdown table (GFM) with PUA, Key Cap, and plain string. The PUA and Key Cap columns are formatted as Unicode Scalar Values, e.g. U+0061, and the plain string is the original key cap string.
- .json: returns the final aggregated data as an array of strings, which can be joined to form a JSON blob of an object with a single member, map
, which is an array of objects.
|
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.includecounts.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.includecounts.md
new file mode 100644
index 00000000000..e105b24b29d
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.includecounts.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskCharacterUseOptions](./kmc-analyze.analyzeoskcharacteruseoptions.md) > [includeCounts](./kmc-analyze.analyzeoskcharacteruseoptions.includecounts.md)
+
+## AnalyzeOskCharacterUseOptions.includeCounts property
+
+If true, reports number of references to each character found in each source file
+
+**Signature:**
+
+```typescript
+includeCounts?: boolean;
+```
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.md
new file mode 100644
index 00000000000..cfaa60ffb34
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskCharacterUseOptions](./kmc-analyze.analyzeoskcharacteruseoptions.md)
+
+## AnalyzeOskCharacterUseOptions interface
+
+Options for character analysis
+
+**Signature:**
+
+```typescript
+export interface AnalyzeOskCharacterUseOptions
+```
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [includeCounts?](./kmc-analyze.analyzeoskcharacteruseoptions.includecounts.md) | | boolean | _(Optional)_ If true, reports number of references to each character found in each source file |
+| [puaBase?](./kmc-analyze.analyzeoskcharacteruseoptions.puabase.md) | | number | _(Optional)_ First character to use in PUA for remapping with &displayMap, defaults to U+F100 |
+| [stripDottedCircle?](./kmc-analyze.analyzeoskcharacteruseoptions.stripdottedcircle.md) | | boolean | _(Optional)_ If true, strips U+25CC from the key cap before further analysis |
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.puabase.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.puabase.md
new file mode 100644
index 00000000000..e1ff19b2184
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.puabase.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskCharacterUseOptions](./kmc-analyze.analyzeoskcharacteruseoptions.md) > [puaBase](./kmc-analyze.analyzeoskcharacteruseoptions.puabase.md)
+
+## AnalyzeOskCharacterUseOptions.puaBase property
+
+First character to use in PUA for remapping with &displayMap, defaults to U+F100
+
+**Signature:**
+
+```typescript
+puaBase?: number;
+```
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.stripdottedcircle.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.stripdottedcircle.md
new file mode 100644
index 00000000000..db975bf710d
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.stripdottedcircle.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskCharacterUseOptions](./kmc-analyze.analyzeoskcharacteruseoptions.md) > [stripDottedCircle](./kmc-analyze.analyzeoskcharacteruseoptions.stripdottedcircle.md)
+
+## AnalyzeOskCharacterUseOptions.stripDottedCircle property
+
+If true, strips U+25CC from the key cap before further analysis
+
+**Signature:**
+
+```typescript
+stripDottedCircle?: boolean;
+```
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua._constructor_.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua._constructor_.md
new file mode 100644
index 00000000000..a0d5f23e35c
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua._constructor_.md
@@ -0,0 +1,20 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskRewritePua](./kmc-analyze.analyzeoskrewritepua.md) > [(constructor)](./kmc-analyze.analyzeoskrewritepua._constructor_.md)
+
+## AnalyzeOskRewritePua.(constructor)
+
+Constructs a new instance of the `AnalyzeOskRewritePua` class
+
+**Signature:**
+
+```typescript
+constructor(callbacks: CompilerCallbacks);
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| callbacks | CompilerCallbacks | |
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.analyze.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.analyze.md
new file mode 100644
index 00000000000..7cdaa00da60
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.analyze.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskRewritePua](./kmc-analyze.analyzeoskrewritepua.md) > [analyze](./kmc-analyze.analyzeoskrewritepua.analyze.md)
+
+## AnalyzeOskRewritePua.analyze() method
+
+Analyze a keyboard file or files, and provide a remapped output. Accepts a .kmn, .kvks, .keyman-touch-layout file formats. For .kmn, will rewrite associated On Screen Keyboard file formats. Can be called multiple times to rewrite multiple files. Use the [AnalyzeOskRewritePua.data](./kmc-analyze.analyzeoskrewritepua.data.md) property to retrieve the output file content for writing. This does not modify the source file.
+
+**Signature:**
+
+```typescript
+analyze(file: string, mapping: Osk.StringResult[]): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| file | string | relative or absolute path to a Keyman source file |
+| mapping | Osk.StringResult\[\] | OSK keycap map provided by [AnalyzeOskCharacterUse](./kmc-analyze.analyzeoskcharacteruse.md) |
+
+**Returns:**
+
+Promise<boolean>
+
+true if the file is successfully loaded and rewritten
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.clear.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.clear.md
new file mode 100644
index 00000000000..001d0c494b2
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.clear.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskRewritePua](./kmc-analyze.analyzeoskrewritepua.md) > [clear](./kmc-analyze.analyzeoskrewritepua.clear.md)
+
+## AnalyzeOskRewritePua.clear() method
+
+Clears data collected from previous calls to [AnalyzeOskRewritePua.analyze()](./kmc-analyze.analyzeoskrewritepua.analyze.md)
+
+**Signature:**
+
+```typescript
+clear(): void;
+```
+**Returns:**
+
+void
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.data.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.data.md
new file mode 100644
index 00000000000..9e3091ca7cb
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.data.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskRewritePua](./kmc-analyze.analyzeoskrewritepua.md) > [data](./kmc-analyze.analyzeoskrewritepua.data.md)
+
+## AnalyzeOskRewritePua.data property
+
+Returns the file data for OSK files rewritten with PUA characters, for use with `&displayMap`.
+
+**Signature:**
+
+```typescript
+get data(): {
+ [index: string]: Uint8Array;
+ };
+```
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.md
new file mode 100644
index 00000000000..731b90bfb64
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.md
@@ -0,0 +1,33 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskRewritePua](./kmc-analyze.analyzeoskrewritepua.md)
+
+## AnalyzeOskRewritePua class
+
+Rewrite On Screen Keyboard files (.kvks, .keyman-touch-layout) with PUA codepoints, based on analysis provided by [AnalyzeOskCharacterUse](./kmc-analyze.analyzeoskcharacteruse.md) class.
+
+**Signature:**
+
+```typescript
+export declare class AnalyzeOskRewritePua
+```
+
+## Constructors
+
+| Constructor | Modifiers | Description |
+| --- | --- | --- |
+| [(constructor)(callbacks)](./kmc-analyze.analyzeoskrewritepua._constructor_.md) | | Constructs a new instance of the AnalyzeOskRewritePua
class |
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [data](./kmc-analyze.analyzeoskrewritepua.data.md) | readonly
| { \[index: string\]: Uint8Array; } | Returns the file data for OSK files rewritten with PUA characters, for use with &displayMap
. |
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [analyze(file, mapping)](./kmc-analyze.analyzeoskrewritepua.analyze.md) | | Analyze a keyboard file or files, and provide a remapped output. Accepts a .kmn, .kvks, .keyman-touch-layout file formats. For .kmn, will rewrite associated On Screen Keyboard file formats. Can be called multiple times to rewrite multiple files. Use the [AnalyzeOskRewritePua.data](./kmc-analyze.analyzeoskrewritepua.data.md) property to retrieve the output file content for writing. This does not modify the source file. |
+| [clear()](./kmc-analyze.analyzeoskrewritepua.clear.md) | | Clears data collected from previous calls to [AnalyzeOskRewritePua.analyze()](./kmc-analyze.analyzeoskrewritepua.analyze.md) |
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.md b/developer/docs/help/reference/api/kmc-analyze.md
new file mode 100644
index 00000000000..3b514bcf962
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md)
+
+## kmc-analyze package
+
+kmc-analyze - keyboard analysis classes, including tools for `&displayMap`.
+
+## Classes
+
+| Class | Description |
+| --- | --- |
+| [AnalyzeOskCharacterUse](./kmc-analyze.analyzeoskcharacteruse.md) | Analyze the characters used in On Screen Keyboard files (.kvks, .keyman-touch-layout) for use with &displayMap
. |
+| [AnalyzeOskRewritePua](./kmc-analyze.analyzeoskrewritepua.md) | Rewrite On Screen Keyboard files (.kvks, .keyman-touch-layout) with PUA codepoints, based on analysis provided by [AnalyzeOskCharacterUse](./kmc-analyze.analyzeoskcharacteruse.md) class. |
+
+## Interfaces
+
+| Interface | Description |
+| --- | --- |
+| [AnalyzeOskCharacterUseOptions](./kmc-analyze.analyzeoskcharacteruseoptions.md) | Options for character analysis |
+
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler._constructor_.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler._constructor_.md
new file mode 100644
index 00000000000..0196e3ca32f
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler._constructor_.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompiler](./kmc-keyboard-info.keyboardinfocompiler.md) > [(constructor)](./kmc-keyboard-info.keyboardinfocompiler._constructor_.md)
+
+## KeyboardInfoCompiler.(constructor)
+
+Constructs a new instance of the `KeyboardInfoCompiler` class
+
+**Signature:**
+
+```typescript
+constructor();
+```
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.init.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.init.md
new file mode 100644
index 00000000000..fb9b1460444
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.init.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompiler](./kmc-keyboard-info.keyboardinfocompiler.md) > [init](./kmc-keyboard-info.keyboardinfocompiler.init.md)
+
+## KeyboardInfoCompiler.init() method
+
+Initialize the compiler. Copies options.
+
+**Signature:**
+
+```typescript
+init(callbacks: CompilerCallbacks, options: KeyboardInfoCompilerOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| callbacks | CompilerCallbacks | Callbacks for external interfaces, including message reporting and file io |
+| options | [KeyboardInfoCompilerOptions](./kmc-keyboard-info.keyboardinfocompileroptions.md) | Compiler options |
+
+**Returns:**
+
+Promise<boolean>
+
+false if initialization fails
+
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.md
new file mode 100644
index 00000000000..8bca9bd0e32
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.md
@@ -0,0 +1,29 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompiler](./kmc-keyboard-info.keyboardinfocompiler.md)
+
+## KeyboardInfoCompiler class
+
+Compiles source data from a keyboard project to a .keyboard\_info. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO.
+
+**Signature:**
+
+```typescript
+export declare class KeyboardInfoCompiler implements KeymanCompiler
+```
+**Implements:** KeymanCompiler
+
+## Constructors
+
+| Constructor | Modifiers | Description |
+| --- | --- | --- |
+| [(constructor)()](./kmc-keyboard-info.keyboardinfocompiler._constructor_.md) | | Constructs a new instance of the KeyboardInfoCompiler
class |
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [init(callbacks, options)](./kmc-keyboard-info.keyboardinfocompiler.init.md) | | Initialize the compiler. Copies options. |
+| [run(inputFilename, outputFilename)](./kmc-keyboard-info.keyboardinfocompiler.run.md) | | Builds a .keyboard\_info file with metadata from the keyboard and package source file. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [KeyboardInfoCompiler.init()](./kmc-keyboard-info.keyboardinfocompiler.init.md) function to read any input files by disk.
This function is intended for use within the keyboards repository. While many of the parameters could be deduced from each other, they are specified here to reduce the number of places the filenames are constructed. For full documentation, see: https://help.keyman.com/developer/cloud/keyboard\_info/
|
+| [write(artifacts)](./kmc-keyboard-info.keyboardinfocompiler.write.md) | | Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
- .keyboard\_info file - metadata file used by keyman.com
|
+
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.run.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.run.md
new file mode 100644
index 00000000000..711d8948648
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.run.md
@@ -0,0 +1,29 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompiler](./kmc-keyboard-info.keyboardinfocompiler.md) > [run](./kmc-keyboard-info.keyboardinfocompiler.run.md)
+
+## KeyboardInfoCompiler.run() method
+
+Builds a .keyboard\_info file with metadata from the keyboard and package source file. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [KeyboardInfoCompiler.init()](./kmc-keyboard-info.keyboardinfocompiler.init.md) function to read any input files by disk.
+
+This function is intended for use within the keyboards repository. While many of the parameters could be deduced from each other, they are specified here to reduce the number of places the filenames are constructed. For full documentation, see: https://help.keyman.com/developer/cloud/keyboard\_info/
+
+**Signature:**
+
+```typescript
+run(inputFilename: string, outputFilename?: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| inputFilename | string | |
+| outputFilename | string | _(Optional)_ |
+
+**Returns:**
+
+Promise<[KeyboardInfoCompilerResult](./kmc-keyboard-info.keyboardinfocompilerresult.md)>
+
+Binary artifacts on success, null on failure.
+
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.write.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.write.md
new file mode 100644
index 00000000000..9ac5bf1b336
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.write.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompiler](./kmc-keyboard-info.keyboardinfocompiler.md) > [write](./kmc-keyboard-info.keyboardinfocompiler.write.md)
+
+## KeyboardInfoCompiler.write() method
+
+Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
+
+- .keyboard\_info file - metadata file used by keyman.com
+
+**Signature:**
+
+```typescript
+write(artifacts: KeyboardInfoCompilerArtifacts): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| artifacts | [KeyboardInfoCompilerArtifacts](./kmc-keyboard-info.keyboardinfocompilerartifacts.md) | object containing artifact binary data to write out |
+
+**Returns:**
+
+Promise<boolean>
+
+true on success
+
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerartifacts.keyboard_info.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerartifacts.keyboard_info.md
new file mode 100644
index 00000000000..e3b46d10e55
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerartifacts.keyboard_info.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompilerArtifacts](./kmc-keyboard-info.keyboardinfocompilerartifacts.md) > [keyboard\_info](./kmc-keyboard-info.keyboardinfocompilerartifacts.keyboard_info.md)
+
+## KeyboardInfoCompilerArtifacts.keyboard\_info property
+
+Binary keyboard info filedata and filename - used by keyman.com
+
+**Signature:**
+
+```typescript
+keyboard_info: KeymanCompilerArtifact;
+```
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerartifacts.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerartifacts.md
new file mode 100644
index 00000000000..fcfa258df4b
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerartifacts.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompilerArtifacts](./kmc-keyboard-info.keyboardinfocompilerartifacts.md)
+
+## KeyboardInfoCompilerArtifacts interface
+
+Internal in-memory build artifacts from a successful compilation
+
+**Signature:**
+
+```typescript
+export interface KeyboardInfoCompilerArtifacts extends KeymanCompilerArtifacts
+```
+**Extends:** KeymanCompilerArtifacts
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [keyboard\_info](./kmc-keyboard-info.keyboardinfocompilerartifacts.keyboard_info.md) | | KeymanCompilerArtifact | Binary keyboard info filedata and filename - used by keyman.com |
+
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompileroptions.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompileroptions.md
new file mode 100644
index 00000000000..63f1279300e
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompileroptions.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompilerOptions](./kmc-keyboard-info.keyboardinfocompileroptions.md)
+
+## KeyboardInfoCompilerOptions interface
+
+Options for the .keyboard\_info compiler
+
+**Signature:**
+
+```typescript
+export interface KeyboardInfoCompilerOptions extends CompilerOptions
+```
+**Extends:** CompilerOptions
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [sources](./kmc-keyboard-info.keyboardinfocompileroptions.sources.md) | | [KeyboardInfoSources](./kmc-keyboard-info.keyboardinfosources.md) | Description of sources and metadata required to build a .keyboard\_info file |
+
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompileroptions.sources.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompileroptions.sources.md
new file mode 100644
index 00000000000..371f1734776
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompileroptions.sources.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompilerOptions](./kmc-keyboard-info.keyboardinfocompileroptions.md) > [sources](./kmc-keyboard-info.keyboardinfocompileroptions.sources.md)
+
+## KeyboardInfoCompilerOptions.sources property
+
+Description of sources and metadata required to build a .keyboard\_info file
+
+**Signature:**
+
+```typescript
+sources: KeyboardInfoSources;
+```
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerresult.artifacts.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerresult.artifacts.md
new file mode 100644
index 00000000000..0554d415c53
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerresult.artifacts.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompilerResult](./kmc-keyboard-info.keyboardinfocompilerresult.md) > [artifacts](./kmc-keyboard-info.keyboardinfocompilerresult.artifacts.md)
+
+## KeyboardInfoCompilerResult.artifacts property
+
+Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [KeyboardInfoCompiler.write()](./kmc-keyboard-info.keyboardinfocompiler.write.md)
+
+**Signature:**
+
+```typescript
+artifacts: KeyboardInfoCompilerArtifacts;
+```
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerresult.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerresult.md
new file mode 100644
index 00000000000..96de0472e3b
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerresult.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompilerResult](./kmc-keyboard-info.keyboardinfocompilerresult.md)
+
+## KeyboardInfoCompilerResult interface
+
+Build artifacts from the .keyboard\_info compiler
+
+**Signature:**
+
+```typescript
+export interface KeyboardInfoCompilerResult extends KeymanCompilerResult
+```
+**Extends:** KeymanCompilerResult
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [artifacts](./kmc-keyboard-info.keyboardinfocompilerresult.artifacts.md) | | [KeyboardInfoCompilerArtifacts](./kmc-keyboard-info.keyboardinfocompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [KeyboardInfoCompiler.write()](./kmc-keyboard-info.keyboardinfocompiler.write.md) |
+
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.forpublishing.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.forpublishing.md
new file mode 100644
index 00000000000..5eb9fe6d6f9
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.forpublishing.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoSources](./kmc-keyboard-info.keyboardinfosources.md) > [forPublishing](./kmc-keyboard-info.keyboardinfosources.forpublishing.md)
+
+## KeyboardInfoSources.forPublishing property
+
+Return an error if project does not meet requirements of keyboards repository
+
+**Signature:**
+
+```typescript
+forPublishing: boolean;
+```
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.jsfilename.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.jsfilename.md
new file mode 100644
index 00000000000..15e0557724b
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.jsfilename.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoSources](./kmc-keyboard-info.keyboardinfosources.md) > [jsFilename](./kmc-keyboard-info.keyboardinfosources.jsfilename.md)
+
+## KeyboardInfoSources.jsFilename property
+
+The compiled keyboard filename and relative path (.js only)
+
+**Signature:**
+
+```typescript
+jsFilename?: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.kmpfilename.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.kmpfilename.md
new file mode 100644
index 00000000000..53689423218
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.kmpfilename.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoSources](./kmc-keyboard-info.keyboardinfosources.md) > [kmpFilename](./kmc-keyboard-info.keyboardinfosources.kmpfilename.md)
+
+## KeyboardInfoSources.kmpFilename property
+
+The compiled package filename and relative path (.kmp)
+
+**Signature:**
+
+```typescript
+kmpFilename: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.kpsfilename.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.kpsfilename.md
new file mode 100644
index 00000000000..577820f1454
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.kpsfilename.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoSources](./kmc-keyboard-info.keyboardinfosources.md) > [kpsFilename](./kmc-keyboard-info.keyboardinfosources.kpsfilename.md)
+
+## KeyboardInfoSources.kpsFilename property
+
+The source package filename and relative path (.kps)
+
+**Signature:**
+
+```typescript
+kpsFilename: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.lastcommitdate.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.lastcommitdate.md
new file mode 100644
index 00000000000..398a0ab9552
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.lastcommitdate.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoSources](./kmc-keyboard-info.keyboardinfosources.md) > [lastCommitDate](./kmc-keyboard-info.keyboardinfosources.lastcommitdate.md)
+
+## KeyboardInfoSources.lastCommitDate property
+
+Last modification date for files in the project folder 'YYYY-MM-DDThh:mm:ssZ'
+
+**Signature:**
+
+```typescript
+lastCommitDate?: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.md
new file mode 100644
index 00000000000..5a6caf26d68
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.md
@@ -0,0 +1,25 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoSources](./kmc-keyboard-info.keyboardinfosources.md)
+
+## KeyboardInfoSources interface
+
+Description of sources and metadata required to build a .keyboard\_info file
+
+**Signature:**
+
+```typescript
+export interface KeyboardInfoSources
+```
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [forPublishing](./kmc-keyboard-info.keyboardinfosources.forpublishing.md) | | boolean | Return an error if project does not meet requirements of keyboards repository |
+| [jsFilename?](./kmc-keyboard-info.keyboardinfosources.jsfilename.md) | | string | _(Optional)_ The compiled keyboard filename and relative path (.js only) |
+| [kmpFilename](./kmc-keyboard-info.keyboardinfosources.kmpfilename.md) | | string | The compiled package filename and relative path (.kmp) |
+| [kpsFilename](./kmc-keyboard-info.keyboardinfosources.kpsfilename.md) | | string | The source package filename and relative path (.kps) |
+| [lastCommitDate?](./kmc-keyboard-info.keyboardinfosources.lastcommitdate.md) | | string | _(Optional)_ Last modification date for files in the project folder 'YYYY-MM-DDThh:mm:ssZ' |
+| [sourcePath](./kmc-keyboard-info.keyboardinfosources.sourcepath.md) | | string | The path in the keymanapp/keyboards repo where this keyboard may be found |
+
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.sourcepath.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.sourcepath.md
new file mode 100644
index 00000000000..197a5d123be
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.sourcepath.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoSources](./kmc-keyboard-info.keyboardinfosources.md) > [sourcePath](./kmc-keyboard-info.keyboardinfosources.sourcepath.md)
+
+## KeyboardInfoSources.sourcePath property
+
+The path in the keymanapp/keyboards repo where this keyboard may be found
+
+**Signature:**
+
+```typescript
+sourcePath: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.md b/developer/docs/help/reference/api/kmc-keyboard-info.md
new file mode 100644
index 00000000000..403add9e832
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md)
+
+## kmc-keyboard-info package
+
+## Classes
+
+| Class | Description |
+| --- | --- |
+| [KeyboardInfoCompiler](./kmc-keyboard-info.keyboardinfocompiler.md) | Compiles source data from a keyboard project to a .keyboard\_info. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO. |
+
+## Interfaces
+
+| Interface | Description |
+| --- | --- |
+| [KeyboardInfoCompilerArtifacts](./kmc-keyboard-info.keyboardinfocompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation |
+| [KeyboardInfoCompilerOptions](./kmc-keyboard-info.keyboardinfocompileroptions.md) | Options for the .keyboard\_info compiler |
+| [KeyboardInfoCompilerResult](./kmc-keyboard-info.keyboardinfocompilerresult.md) | Build artifacts from the .keyboard\_info compiler |
+| [KeyboardInfoSources](./kmc-keyboard-info.keyboardinfosources.md) | Description of sources and metadata required to build a .keyboard\_info file |
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompiler._constructor_.md b/developer/docs/help/reference/api/kmc-kmn.kmncompiler._constructor_.md
new file mode 100644
index 00000000000..42a1cd666cb
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompiler._constructor_.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompiler](./kmc-kmn.kmncompiler.md) > [(constructor)](./kmc-kmn.kmncompiler._constructor_.md)
+
+## KmnCompiler.(constructor)
+
+Constructs a new instance of the `KmnCompiler` class
+
+**Signature:**
+
+```typescript
+constructor();
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompiler.init.md b/developer/docs/help/reference/api/kmc-kmn.kmncompiler.init.md
new file mode 100644
index 00000000000..5d2698b5443
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompiler.init.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompiler](./kmc-kmn.kmncompiler.md) > [init](./kmc-kmn.kmncompiler.init.md)
+
+## KmnCompiler.init() method
+
+Initialize the compiler, including loading the WASM host for kmcmplib. Copies options.
+
+**Signature:**
+
+```typescript
+init(callbacks: CompilerCallbacks, options: KmnCompilerOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| callbacks | CompilerCallbacks | Callbacks for external interfaces, including message reporting and file io |
+| options | [KmnCompilerOptions](./kmc-kmn.kmncompileroptions.md) | Compiler options |
+
+**Returns:**
+
+Promise<boolean>
+
+false if initialization fails
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompiler.md b/developer/docs/help/reference/api/kmc-kmn.kmncompiler.md
new file mode 100644
index 00000000000..d41c05fc2e3
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompiler.md
@@ -0,0 +1,30 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompiler](./kmc-kmn.kmncompiler.md)
+
+## KmnCompiler class
+
+Compiles a .kmn file to a .kmx, .kvk, and/or .js. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO.
+
+**Signature:**
+
+```typescript
+export declare class KmnCompiler implements KeymanCompiler, UnicodeSetParser
+```
+**Implements:** KeymanCompiler, UnicodeSetParser
+
+## Constructors
+
+| Constructor | Modifiers | Description |
+| --- | --- | --- |
+| [(constructor)()](./kmc-kmn.kmncompiler._constructor_.md) | | Constructs a new instance of the KmnCompiler
class |
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [init(callbacks, options)](./kmc-kmn.kmncompiler.init.md) | | Initialize the compiler, including loading the WASM host for kmcmplib. Copies options. |
+| [run(infile, outfile)](./kmc-kmn.kmncompiler.run.md) | | Compiles a .kmn file to .kmx, .kvk, and/or .js files. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [KmnCompiler.init()](./kmc-kmn.kmncompiler.init.md) function to read any input files by disk. |
+| [verifyInitialized()](./kmc-kmn.kmncompiler.verifyinitialized.md) | | Verify that wasm is spun up OK. |
+| [write(artifacts)](./kmc-kmn.kmncompiler.write.md) | | Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
- .kmx file - binary keyboard used by Keyman on desktop platforms - .kvk file - binary on screen keyboard used by Keyman on desktop platforms - .js file - Javascript keyboard for web and touch platforms
|
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompiler.run.md b/developer/docs/help/reference/api/kmc-kmn.kmncompiler.run.md
new file mode 100644
index 00000000000..a97f40ab962
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompiler.run.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompiler](./kmc-kmn.kmncompiler.md) > [run](./kmc-kmn.kmncompiler.run.md)
+
+## KmnCompiler.run() method
+
+Compiles a .kmn file to .kmx, .kvk, and/or .js files. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [KmnCompiler.init()](./kmc-kmn.kmncompiler.init.md) function to read any input files by disk.
+
+**Signature:**
+
+```typescript
+run(infile: string, outfile: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| infile | string | Path to source file. Path will be parsed to find relative references in the .kmn file, such as icon or On Screen Keyboard file |
+| outfile | string | Path to output file. The file will not be written to, but will be included in the result for use by [KmnCompiler.write()](./kmc-kmn.kmncompiler.write.md). |
+
+**Returns:**
+
+Promise<[KmnCompilerResult](./kmc-kmn.kmncompilerresult.md)>
+
+Binary artifacts on success, null on failure.
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompiler.verifyinitialized.md b/developer/docs/help/reference/api/kmc-kmn.kmncompiler.verifyinitialized.md
new file mode 100644
index 00000000000..f2d38795172
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompiler.verifyinitialized.md
@@ -0,0 +1,19 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompiler](./kmc-kmn.kmncompiler.md) > [verifyInitialized](./kmc-kmn.kmncompiler.verifyinitialized.md)
+
+## KmnCompiler.verifyInitialized() method
+
+Verify that wasm is spun up OK.
+
+**Signature:**
+
+```typescript
+verifyInitialized(): boolean;
+```
+**Returns:**
+
+boolean
+
+true if OK
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompiler.write.md b/developer/docs/help/reference/api/kmc-kmn.kmncompiler.write.md
new file mode 100644
index 00000000000..ebc1b8d8437
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompiler.write.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompiler](./kmc-kmn.kmncompiler.md) > [write](./kmc-kmn.kmncompiler.write.md)
+
+## KmnCompiler.write() method
+
+Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
+
+- .kmx file - binary keyboard used by Keyman on desktop platforms - .kvk file - binary on screen keyboard used by Keyman on desktop platforms - .js file - Javascript keyboard for web and touch platforms
+
+**Signature:**
+
+```typescript
+write(artifacts: KmnCompilerArtifacts): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| artifacts | [KmnCompilerArtifacts](./kmc-kmn.kmncompilerartifacts.md) | object containing artifact binary data to write out |
+
+**Returns:**
+
+Promise<boolean>
+
+true on success
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.js.md b/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.js.md
new file mode 100644
index 00000000000..12706601f1e
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.js.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompilerArtifacts](./kmc-kmn.kmncompilerartifacts.md) > [js](./kmc-kmn.kmncompilerartifacts.js.md)
+
+## KmnCompilerArtifacts.js property
+
+Javascript keyboard filedata and filename - installable into KeymanWeb, Keyman mobile products
+
+**Signature:**
+
+```typescript
+js?: KeymanCompilerArtifactOptional;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.kmx.md b/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.kmx.md
new file mode 100644
index 00000000000..c850b6d036b
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.kmx.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompilerArtifacts](./kmc-kmn.kmncompilerartifacts.md) > [kmx](./kmc-kmn.kmncompilerartifacts.kmx.md)
+
+## KmnCompilerArtifacts.kmx property
+
+Binary keyboard filedata and filename - installable into Keyman desktop projects
+
+**Signature:**
+
+```typescript
+kmx?: KeymanCompilerArtifactOptional;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.kvk.md b/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.kvk.md
new file mode 100644
index 00000000000..4a6bb87d65a
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.kvk.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompilerArtifacts](./kmc-kmn.kmncompilerartifacts.md) > [kvk](./kmc-kmn.kmncompilerartifacts.kvk.md)
+
+## KmnCompilerArtifacts.kvk property
+
+Binary on screen keyboard filedata and filename - installable into Keyman desktop projects alongside .kmx
+
+**Signature:**
+
+```typescript
+kvk?: KeymanCompilerArtifactOptional;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.md b/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.md
new file mode 100644
index 00000000000..37561d34a02
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompilerArtifacts](./kmc-kmn.kmncompilerartifacts.md)
+
+## KmnCompilerArtifacts interface
+
+Internal in-memory build artifacts from a successful compilation
+
+**Signature:**
+
+```typescript
+export interface KmnCompilerArtifacts extends KeymanCompilerArtifacts
+```
+**Extends:** KeymanCompilerArtifacts
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [js?](./kmc-kmn.kmncompilerartifacts.js.md) | | KeymanCompilerArtifactOptional | _(Optional)_ Javascript keyboard filedata and filename - installable into KeymanWeb, Keyman mobile products |
+| [kmx?](./kmc-kmn.kmncompilerartifacts.kmx.md) | | KeymanCompilerArtifactOptional | _(Optional)_ Binary keyboard filedata and filename - installable into Keyman desktop projects |
+| [kvk?](./kmc-kmn.kmncompilerartifacts.kvk.md) | | KeymanCompilerArtifactOptional | _(Optional)_ Binary on screen keyboard filedata and filename - installable into Keyman desktop projects alongside .kmx |
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompileroptions.md b/developer/docs/help/reference/api/kmc-kmn.kmncompileroptions.md
new file mode 100644
index 00000000000..1591a9313b6
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompileroptions.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompilerOptions](./kmc-kmn.kmncompileroptions.md)
+
+## KmnCompilerOptions interface
+
+Options for the .kmn compiler
+
+**Signature:**
+
+```typescript
+export interface KmnCompilerOptions extends CompilerOptions
+```
+**Extends:** CompilerOptions
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.artifacts.md b/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.artifacts.md
new file mode 100644
index 00000000000..12a4dac8596
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.artifacts.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompilerResult](./kmc-kmn.kmncompilerresult.md) > [artifacts](./kmc-kmn.kmncompilerresult.artifacts.md)
+
+## KmnCompilerResult.artifacts property
+
+Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [KmnCompiler.write()](./kmc-kmn.kmncompiler.write.md)
+
+**Signature:**
+
+```typescript
+artifacts: KmnCompilerArtifacts;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.displaymap.md b/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.displaymap.md
new file mode 100644
index 00000000000..cdf6cbe9a4f
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.displaymap.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompilerResult](./kmc-kmn.kmncompilerresult.md) > [displayMap](./kmc-kmn.kmncompilerresult.displaymap.md)
+
+## KmnCompilerResult.displayMap property
+
+Mapping data for `&displayMap`, intended for use by kmc-analyze
+
+**Signature:**
+
+```typescript
+displayMap?: Osk.PuaMap;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.extra.md b/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.extra.md
new file mode 100644
index 00000000000..9263bb78037
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.extra.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompilerResult](./kmc-kmn.kmncompilerresult.md) > [extra](./kmc-kmn.kmncompilerresult.extra.md)
+
+## KmnCompilerResult.extra property
+
+Internal additional metadata used by secondary compile phases such as KmwCompiler, not intended for external use
+
+**Signature:**
+
+```typescript
+extra: KmnCompilerResultExtra;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.md b/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.md
new file mode 100644
index 00000000000..b50a6025b41
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompilerResult](./kmc-kmn.kmncompilerresult.md)
+
+## KmnCompilerResult interface
+
+Build artifacts from the .kmn compiler
+
+**Signature:**
+
+```typescript
+export interface KmnCompilerResult extends KeymanCompilerResult
+```
+**Extends:** KeymanCompilerResult
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [artifacts](./kmc-kmn.kmncompilerresult.artifacts.md) | | [KmnCompilerArtifacts](./kmc-kmn.kmncompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [KmnCompiler.write()](./kmc-kmn.kmncompiler.write.md) |
+| [displayMap?](./kmc-kmn.kmncompilerresult.displaymap.md) | | [Osk.PuaMap](./kmc-kmn.osk.puamap.md) | _(Optional)_ Mapping data for &displayMap
, intended for use by kmc-analyze |
+| [extra](./kmc-kmn.kmncompilerresult.extra.md) | | KmnCompilerResultExtra | Internal additional metadata used by secondary compile phases such as KmwCompiler, not intended for external use |
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.md b/developer/docs/help/reference/api/kmc-kmn.md
new file mode 100644
index 00000000000..253dbbac62b
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md)
+
+## kmc-kmn package
+
+kmc-kmn - Keyman keyboard compiler
+
+## Classes
+
+| Class | Description |
+| --- | --- |
+| [KmnCompiler](./kmc-kmn.kmncompiler.md) | Compiles a .kmn file to a .kmx, .kvk, and/or .js. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO. |
+
+## Interfaces
+
+| Interface | Description |
+| --- | --- |
+| [KmnCompilerArtifacts](./kmc-kmn.kmncompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation |
+| [KmnCompilerOptions](./kmc-kmn.kmncompileroptions.md) | Options for the .kmn compiler |
+| [KmnCompilerResult](./kmc-kmn.kmncompilerresult.md) | Build artifacts from the .kmn compiler |
+
+## Namespaces
+
+| Namespace | Description |
+| --- | --- |
+| [Osk](./kmc-kmn.osk.md) | |
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.md b/developer/docs/help/reference/api/kmc-kmn.osk.md
new file mode 100644
index 00000000000..7257d523fec
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md)
+
+## Osk namespace
+
+## Functions
+
+| Function | Description |
+| --- | --- |
+| [parseMapping(mapping)](./kmc-kmn.osk.parsemapping.md) | |
+| [remapTouchLayout(source, map)](./kmc-kmn.osk.remaptouchlayout.md) | |
+| [remapVisualKeyboard(vk, map)](./kmc-kmn.osk.remapvisualkeyboard.md) | |
+
+## Interfaces
+
+| Interface | Description |
+| --- | --- |
+| [StringRef](./kmc-kmn.osk.stringref.md) | |
+| [StringRefUsage](./kmc-kmn.osk.stringrefusage.md) | |
+| [StringResult](./kmc-kmn.osk.stringresult.md) | Represents a single key cap found by AnalyzeOskCharacterUse
|
+
+## Type Aliases
+
+| Type Alias | Description |
+| --- | --- |
+| [PuaMap](./kmc-kmn.osk.puamap.md) | |
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.parsemapping.md b/developer/docs/help/reference/api/kmc-kmn.osk.parsemapping.md
new file mode 100644
index 00000000000..d2dbbdffe50
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.parsemapping.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [parseMapping](./kmc-kmn.osk.parsemapping.md)
+
+## Osk.parseMapping() function
+
+**Signature:**
+
+```typescript
+export declare function parseMapping(mapping: any): PuaMap;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| mapping | any | |
+
+**Returns:**
+
+[PuaMap](./kmc-kmn.osk.puamap.md)
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.puamap.md b/developer/docs/help/reference/api/kmc-kmn.osk.puamap.md
new file mode 100644
index 00000000000..4a56d392123
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.puamap.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [PuaMap](./kmc-kmn.osk.puamap.md)
+
+## Osk.PuaMap type
+
+**Signature:**
+
+```typescript
+export type PuaMap = {
+ [index: string]: string;
+};
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.remaptouchlayout.md b/developer/docs/help/reference/api/kmc-kmn.osk.remaptouchlayout.md
new file mode 100644
index 00000000000..b6c8c76239f
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.remaptouchlayout.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [remapTouchLayout](./kmc-kmn.osk.remaptouchlayout.md)
+
+## Osk.remapTouchLayout() function
+
+**Signature:**
+
+```typescript
+export declare function remapTouchLayout(source: TouchLayout.TouchLayoutFile, map: PuaMap): boolean;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| source | TouchLayout.TouchLayoutFile | |
+| map | [PuaMap](./kmc-kmn.osk.puamap.md) | |
+
+**Returns:**
+
+boolean
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.remapvisualkeyboard.md b/developer/docs/help/reference/api/kmc-kmn.osk.remapvisualkeyboard.md
new file mode 100644
index 00000000000..b6c41d28032
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.remapvisualkeyboard.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [remapVisualKeyboard](./kmc-kmn.osk.remapvisualkeyboard.md)
+
+## Osk.remapVisualKeyboard() function
+
+**Signature:**
+
+```typescript
+export declare function remapVisualKeyboard(vk: VisualKeyboard.VisualKeyboard, map: PuaMap): boolean;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| vk | VisualKeyboard.VisualKeyboard | |
+| map | [PuaMap](./kmc-kmn.osk.puamap.md) | |
+
+**Returns:**
+
+boolean
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringref.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringref.md
new file mode 100644
index 00000000000..24e66ec891a
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringref.md
@@ -0,0 +1,19 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringRef](./kmc-kmn.osk.stringref.md)
+
+## Osk.StringRef interface
+
+**Signature:**
+
+```typescript
+export interface StringRef
+```
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [str](./kmc-kmn.osk.stringref.str.md) | | string | |
+| [usages](./kmc-kmn.osk.stringref.usages.md) | | [StringRefUsage](./kmc-kmn.osk.stringrefusage.md)\[\] | |
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringref.str.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringref.str.md
new file mode 100644
index 00000000000..85e45efa043
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringref.str.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringRef](./kmc-kmn.osk.stringref.md) > [str](./kmc-kmn.osk.stringref.str.md)
+
+## Osk.StringRef.str property
+
+**Signature:**
+
+```typescript
+str: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringref.usages.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringref.usages.md
new file mode 100644
index 00000000000..8e6c209879a
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringref.usages.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringRef](./kmc-kmn.osk.stringref.md) > [usages](./kmc-kmn.osk.stringref.usages.md)
+
+## Osk.StringRef.usages property
+
+**Signature:**
+
+```typescript
+usages: StringRefUsage[];
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringrefusage.count.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringrefusage.count.md
new file mode 100644
index 00000000000..73cbde62ebd
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringrefusage.count.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringRefUsage](./kmc-kmn.osk.stringrefusage.md) > [count](./kmc-kmn.osk.stringrefusage.count.md)
+
+## Osk.StringRefUsage.count property
+
+**Signature:**
+
+```typescript
+count: number;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringrefusage.filename.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringrefusage.filename.md
new file mode 100644
index 00000000000..f199327033d
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringrefusage.filename.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringRefUsage](./kmc-kmn.osk.stringrefusage.md) > [filename](./kmc-kmn.osk.stringrefusage.filename.md)
+
+## Osk.StringRefUsage.filename property
+
+**Signature:**
+
+```typescript
+filename: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringrefusage.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringrefusage.md
new file mode 100644
index 00000000000..403b0af7fe0
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringrefusage.md
@@ -0,0 +1,19 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringRefUsage](./kmc-kmn.osk.stringrefusage.md)
+
+## Osk.StringRefUsage interface
+
+**Signature:**
+
+```typescript
+export interface StringRefUsage
+```
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [count](./kmc-kmn.osk.stringrefusage.count.md) | | number | |
+| [filename](./kmc-kmn.osk.stringrefusage.filename.md) | | string | |
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.md
new file mode 100644
index 00000000000..1e5a6d10194
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringResult](./kmc-kmn.osk.stringresult.md)
+
+## Osk.StringResult interface
+
+Represents a single key cap found by `AnalyzeOskCharacterUse`
+
+**Signature:**
+
+```typescript
+export interface StringResult
+```
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [pua](./kmc-kmn.osk.stringresult.pua.md) | | string | hexadecimal single character in PUA range, without 'U+' prefix, e.g. 'F100' |
+| [str](./kmc-kmn.osk.stringresult.str.md) | | string | the key cap string |
+| [unicode](./kmc-kmn.osk.stringresult.unicode.md) | | string | unicode code points in for reference, without 'U+' prefix, e.g. '0061 0301' |
+| [usages](./kmc-kmn.osk.stringresult.usages.md) | | [StringRefUsage](./kmc-kmn.osk.stringrefusage.md)\[\] \| string\[\] | files in which the string is referenced; will be an array of if includeCounts is true, otherwise will be an array of strings listing files in which the key cap may be found |
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.pua.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.pua.md
new file mode 100644
index 00000000000..84b2880dc0e
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.pua.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringResult](./kmc-kmn.osk.stringresult.md) > [pua](./kmc-kmn.osk.stringresult.pua.md)
+
+## Osk.StringResult.pua property
+
+hexadecimal single character in PUA range, without 'U+' prefix, e.g. 'F100'
+
+**Signature:**
+
+```typescript
+pua: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.str.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.str.md
new file mode 100644
index 00000000000..e24371b1d89
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.str.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringResult](./kmc-kmn.osk.stringresult.md) > [str](./kmc-kmn.osk.stringresult.str.md)
+
+## Osk.StringResult.str property
+
+the key cap string
+
+**Signature:**
+
+```typescript
+str: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.unicode.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.unicode.md
new file mode 100644
index 00000000000..92c8a789d50
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.unicode.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringResult](./kmc-kmn.osk.stringresult.md) > [unicode](./kmc-kmn.osk.stringresult.unicode.md)
+
+## Osk.StringResult.unicode property
+
+unicode code points in for reference, without 'U+' prefix, e.g. '0061 0301'
+
+**Signature:**
+
+```typescript
+unicode: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.usages.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.usages.md
new file mode 100644
index 00000000000..0010cc8cb89
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.usages.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringResult](./kmc-kmn.osk.stringresult.md) > [usages](./kmc-kmn.osk.stringresult.usages.md)
+
+## Osk.StringResult.usages property
+
+files in which the string is referenced; will be an array of if includeCounts is true, otherwise will be an array of strings listing files in which the key cap may be found
+
+**Signature:**
+
+```typescript
+usages: StringRefUsage[] | string[];
+```
diff --git a/developer/docs/help/reference/api/kmc-ldml.ldmlcompileroptions.md b/developer/docs/help/reference/api/kmc-ldml.ldmlcompileroptions.md
new file mode 100644
index 00000000000..3ee68592870
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-ldml.ldmlcompileroptions.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-ldml](./kmc-ldml.md) > [LdmlCompilerOptions](./kmc-ldml.ldmlcompileroptions.md)
+
+## LdmlCompilerOptions interface
+
+Options for the .xml LDML keyboard compiler
+
+**Signature:**
+
+```typescript
+export interface LdmlCompilerOptions extends CompilerOptions
+```
+**Extends:** CompilerOptions
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [readerOptions](./kmc-ldml.ldmlcompileroptions.readeroptions.md) | | LDMLKeyboardXMLSourceFileReaderOptions | Paths and other options required for reading .xml files |
+
diff --git a/developer/docs/help/reference/api/kmc-ldml.ldmlcompileroptions.readeroptions.md b/developer/docs/help/reference/api/kmc-ldml.ldmlcompileroptions.readeroptions.md
new file mode 100644
index 00000000000..59e3f53b736
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-ldml.ldmlcompileroptions.readeroptions.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-ldml](./kmc-ldml.md) > [LdmlCompilerOptions](./kmc-ldml.ldmlcompileroptions.md) > [readerOptions](./kmc-ldml.ldmlcompileroptions.readeroptions.md)
+
+## LdmlCompilerOptions.readerOptions property
+
+Paths and other options required for reading .xml files
+
+**Signature:**
+
+```typescript
+readerOptions: LDMLKeyboardXMLSourceFileReaderOptions;
+```
diff --git a/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.init.md b/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.init.md
new file mode 100644
index 00000000000..6fd83e9d463
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.init.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-ldml](./kmc-ldml.md) > [LdmlKeyboardCompiler](./kmc-ldml.ldmlkeyboardcompiler.md) > [init](./kmc-ldml.ldmlkeyboardcompiler.init.md)
+
+## LdmlKeyboardCompiler.init() method
+
+Initialize the compiler, including loading the WASM host for uset parsing. Copies options.
+
+**Signature:**
+
+```typescript
+init(callbacks: CompilerCallbacks, options: LdmlCompilerOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| callbacks | CompilerCallbacks | Callbacks for external interfaces, including message reporting and file io |
+| options | [LdmlCompilerOptions](./kmc-ldml.ldmlcompileroptions.md) | Compiler options |
+
+**Returns:**
+
+Promise<boolean>
+
+false if initialization fails
+
diff --git a/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.md b/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.md
new file mode 100644
index 00000000000..fdd5d32c16b
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-ldml](./kmc-ldml.md) > [LdmlKeyboardCompiler](./kmc-ldml.ldmlkeyboardcompiler.md)
+
+## LdmlKeyboardCompiler class
+
+Compiles a LDML keyboard .xml file to a .kmx (KMXPlus), .kvk, and/or .js. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO.
+
+**Signature:**
+
+```typescript
+export declare class LdmlKeyboardCompiler implements KeymanCompiler
+```
+**Implements:** KeymanCompiler
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [init(callbacks, options)](./kmc-ldml.ldmlkeyboardcompiler.init.md) | | Initialize the compiler, including loading the WASM host for uset parsing. Copies options. |
+| [run(inputFilename, outputFilename)](./kmc-ldml.ldmlkeyboardcompiler.run.md) | | Compiles a LDML keyboard .xml file to .kmx, .kvk, and/or .js files. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [LdmlKeyboardCompiler.init()](./kmc-ldml.ldmlkeyboardcompiler.init.md) function to read any input files by disk. |
+| [write(artifacts)](./kmc-ldml.ldmlkeyboardcompiler.write.md) | | Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
- .kmx file - binary keyboard used by Keyman on desktop platforms - .kvk file - binary on screen keyboard used by Keyman on desktop platforms - .js file - Javascript keyboard for web and touch platforms
|
+
diff --git a/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.run.md b/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.run.md
new file mode 100644
index 00000000000..cb0e9eaa7f4
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.run.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-ldml](./kmc-ldml.md) > [LdmlKeyboardCompiler](./kmc-ldml.ldmlkeyboardcompiler.md) > [run](./kmc-ldml.ldmlkeyboardcompiler.run.md)
+
+## LdmlKeyboardCompiler.run() method
+
+Compiles a LDML keyboard .xml file to .kmx, .kvk, and/or .js files. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [LdmlKeyboardCompiler.init()](./kmc-ldml.ldmlkeyboardcompiler.init.md) function to read any input files by disk.
+
+**Signature:**
+
+```typescript
+run(inputFilename: string, outputFilename?: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| inputFilename | string | |
+| outputFilename | string | _(Optional)_ |
+
+**Returns:**
+
+Promise<LdmlKeyboardCompilerResult>
+
+Binary artifacts on success, null on failure.
+
diff --git a/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.write.md b/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.write.md
new file mode 100644
index 00000000000..434b463adb4
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.write.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-ldml](./kmc-ldml.md) > [LdmlKeyboardCompiler](./kmc-ldml.ldmlkeyboardcompiler.md) > [write](./kmc-ldml.ldmlkeyboardcompiler.write.md)
+
+## LdmlKeyboardCompiler.write() method
+
+Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
+
+- .kmx file - binary keyboard used by Keyman on desktop platforms - .kvk file - binary on screen keyboard used by Keyman on desktop platforms - .js file - Javascript keyboard for web and touch platforms
+
+**Signature:**
+
+```typescript
+write(artifacts: LdmlKeyboardCompilerArtifacts): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| artifacts | LdmlKeyboardCompilerArtifacts | object containing artifact binary data to write out |
+
+**Returns:**
+
+Promise<boolean>
+
+true on success
+
diff --git a/developer/docs/help/reference/api/kmc-ldml.md b/developer/docs/help/reference/api/kmc-ldml.md
new file mode 100644
index 00000000000..ca921083a22
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-ldml.md
@@ -0,0 +1,18 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-ldml](./kmc-ldml.md)
+
+## kmc-ldml package
+
+## Classes
+
+| Class | Description |
+| --- | --- |
+| [LdmlKeyboardCompiler](./kmc-ldml.ldmlkeyboardcompiler.md) | Compiles a LDML keyboard .xml file to a .kmx (KMXPlus), .kvk, and/or .js. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO. |
+
+## Interfaces
+
+| Interface | Description |
+| --- | --- |
+| [LdmlCompilerOptions](./kmc-ldml.ldmlcompileroptions.md) | Options for the .xml LDML keyboard compiler |
+
diff --git a/developer/docs/help/reference/api/kmc-model-info.md b/developer/docs/help/reference/api/kmc-model-info.md
new file mode 100644
index 00000000000..f7cbd6d4631
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md)
+
+## kmc-model-info package
+
+## Classes
+
+| Class | Description |
+| --- | --- |
+| [ModelInfoCompiler](./kmc-model-info.modelinfocompiler.md) | Compiles source data from a lexical model project to a .model\_info. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO. |
+| [ModelInfoSources](./kmc-model-info.modelinfosources.md) | Description of sources and metadata required to build a .model\_info file |
+
+## Interfaces
+
+| Interface | Description |
+| --- | --- |
+| [ModelInfoCompilerArtifacts](./kmc-model-info.modelinfocompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation |
+| [ModelInfoCompilerOptions](./kmc-model-info.modelinfocompileroptions.md) | Options for the .model\_info compiler |
+| [ModelInfoCompilerResult](./kmc-model-info.modelinfocompilerresult.md) | Build artifacts from the .model\_info compiler |
+
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler._constructor_.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler._constructor_.md
new file mode 100644
index 00000000000..9aadd791a08
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler._constructor_.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompiler](./kmc-model-info.modelinfocompiler.md) > [(constructor)](./kmc-model-info.modelinfocompiler._constructor_.md)
+
+## ModelInfoCompiler.(constructor)
+
+Constructs a new instance of the `ModelInfoCompiler` class
+
+**Signature:**
+
+```typescript
+constructor();
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.init.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.init.md
new file mode 100644
index 00000000000..ff8383bb27e
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.init.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompiler](./kmc-model-info.modelinfocompiler.md) > [init](./kmc-model-info.modelinfocompiler.init.md)
+
+## ModelInfoCompiler.init() method
+
+Initialize the compiler. Copies options.
+
+**Signature:**
+
+```typescript
+init(callbacks: CompilerCallbacks, options: ModelInfoCompilerOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| callbacks | CompilerCallbacks | Callbacks for external interfaces, including message reporting and file io |
+| options | [ModelInfoCompilerOptions](./kmc-model-info.modelinfocompileroptions.md) | Compiler options |
+
+**Returns:**
+
+Promise<boolean>
+
+false if initialization fails
+
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.md
new file mode 100644
index 00000000000..05b0c58ba68
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.md
@@ -0,0 +1,29 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompiler](./kmc-model-info.modelinfocompiler.md)
+
+## ModelInfoCompiler class
+
+Compiles source data from a lexical model project to a .model\_info. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO.
+
+**Signature:**
+
+```typescript
+export declare class ModelInfoCompiler implements KeymanCompiler
+```
+**Implements:** KeymanCompiler
+
+## Constructors
+
+| Constructor | Modifiers | Description |
+| --- | --- | --- |
+| [(constructor)()](./kmc-model-info.modelinfocompiler._constructor_.md) | | Constructs a new instance of the ModelInfoCompiler
class |
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [init(callbacks, options)](./kmc-model-info.modelinfocompiler.init.md) | | Initialize the compiler. Copies options. |
+| [run(inputFilename, outputFilename)](./kmc-model-info.modelinfocompiler.run.md) | | Builds .model\_info file with metadata from the model and package source file. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [ModelInfoCompiler.init()](./kmc-model-info.modelinfocompiler.init.md) function to read any input files by disk.
This function is intended for use within the lexical-models repository. While many of the parameters could be deduced from each other, they are specified here to reduce the number of places the filenames are constructed.
|
+| [write(artifacts)](./kmc-model-info.modelinfocompiler.write.md) | | Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
- .model\_info file - metadata file used by keyman.com
|
+
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.run.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.run.md
new file mode 100644
index 00000000000..ead3fa50032
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.run.md
@@ -0,0 +1,29 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompiler](./kmc-model-info.modelinfocompiler.md) > [run](./kmc-model-info.modelinfocompiler.run.md)
+
+## ModelInfoCompiler.run() method
+
+Builds .model\_info file with metadata from the model and package source file. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [ModelInfoCompiler.init()](./kmc-model-info.modelinfocompiler.init.md) function to read any input files by disk.
+
+This function is intended for use within the lexical-models repository. While many of the parameters could be deduced from each other, they are specified here to reduce the number of places the filenames are constructed.
+
+**Signature:**
+
+```typescript
+run(inputFilename: string, outputFilename?: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| inputFilename | string | |
+| outputFilename | string | _(Optional)_ |
+
+**Returns:**
+
+Promise<[ModelInfoCompilerResult](./kmc-model-info.modelinfocompilerresult.md)>
+
+Binary artifacts on success, null on failure.
+
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.write.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.write.md
new file mode 100644
index 00000000000..e2d2e5fd27e
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.write.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompiler](./kmc-model-info.modelinfocompiler.md) > [write](./kmc-model-info.modelinfocompiler.write.md)
+
+## ModelInfoCompiler.write() method
+
+Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
+
+- .model\_info file - metadata file used by keyman.com
+
+**Signature:**
+
+```typescript
+write(artifacts: ModelInfoCompilerArtifacts): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| artifacts | [ModelInfoCompilerArtifacts](./kmc-model-info.modelinfocompilerartifacts.md) | object containing artifact binary data to write out |
+
+**Returns:**
+
+Promise<boolean>
+
+true on success
+
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerartifacts.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerartifacts.md
new file mode 100644
index 00000000000..1c7e2e7df65
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerartifacts.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompilerArtifacts](./kmc-model-info.modelinfocompilerartifacts.md)
+
+## ModelInfoCompilerArtifacts interface
+
+Internal in-memory build artifacts from a successful compilation
+
+**Signature:**
+
+```typescript
+export interface ModelInfoCompilerArtifacts extends KeymanCompilerArtifacts
+```
+**Extends:** KeymanCompilerArtifacts
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [model\_info](./kmc-model-info.modelinfocompilerartifacts.model_info.md) | | KeymanCompilerArtifact | Binary model info filedata and filename - used by keyman.com |
+
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerartifacts.model_info.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerartifacts.model_info.md
new file mode 100644
index 00000000000..1f66e96f344
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerartifacts.model_info.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompilerArtifacts](./kmc-model-info.modelinfocompilerartifacts.md) > [model\_info](./kmc-model-info.modelinfocompilerartifacts.model_info.md)
+
+## ModelInfoCompilerArtifacts.model\_info property
+
+Binary model info filedata and filename - used by keyman.com
+
+**Signature:**
+
+```typescript
+model_info: KeymanCompilerArtifact;
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompileroptions.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompileroptions.md
new file mode 100644
index 00000000000..984d9bf70a7
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompileroptions.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompilerOptions](./kmc-model-info.modelinfocompileroptions.md)
+
+## ModelInfoCompilerOptions interface
+
+Options for the .model\_info compiler
+
+**Signature:**
+
+```typescript
+export interface ModelInfoCompilerOptions extends CompilerOptions
+```
+**Extends:** CompilerOptions
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [sources](./kmc-model-info.modelinfocompileroptions.sources.md) | | [ModelInfoSources](./kmc-model-info.modelinfosources.md) | Description of sources and metadata required to build a .model\_info file |
+
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompileroptions.sources.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompileroptions.sources.md
new file mode 100644
index 00000000000..40c986596e9
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompileroptions.sources.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompilerOptions](./kmc-model-info.modelinfocompileroptions.md) > [sources](./kmc-model-info.modelinfocompileroptions.sources.md)
+
+## ModelInfoCompilerOptions.sources property
+
+Description of sources and metadata required to build a .model\_info file
+
+**Signature:**
+
+```typescript
+sources: ModelInfoSources;
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerresult.artifacts.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerresult.artifacts.md
new file mode 100644
index 00000000000..9dc3bdf6ea4
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerresult.artifacts.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompilerResult](./kmc-model-info.modelinfocompilerresult.md) > [artifacts](./kmc-model-info.modelinfocompilerresult.artifacts.md)
+
+## ModelInfoCompilerResult.artifacts property
+
+Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [ModelInfoCompiler.write()](./kmc-model-info.modelinfocompiler.write.md)
+
+**Signature:**
+
+```typescript
+artifacts: ModelInfoCompilerArtifacts;
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerresult.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerresult.md
new file mode 100644
index 00000000000..0980afd7f76
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerresult.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompilerResult](./kmc-model-info.modelinfocompilerresult.md)
+
+## ModelInfoCompilerResult interface
+
+Build artifacts from the .model\_info compiler
+
+**Signature:**
+
+```typescript
+export interface ModelInfoCompilerResult extends KeymanCompilerResult
+```
+**Extends:** KeymanCompilerResult
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [artifacts](./kmc-model-info.modelinfocompilerresult.artifacts.md) | | [ModelInfoCompilerArtifacts](./kmc-model-info.modelinfocompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [ModelInfoCompiler.write()](./kmc-model-info.modelinfocompiler.write.md) |
+
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfosources.forpublishing.md b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.forpublishing.md
new file mode 100644
index 00000000000..0877529d209
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.forpublishing.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoSources](./kmc-model-info.modelinfosources.md) > [forPublishing](./kmc-model-info.modelinfosources.forpublishing.md)
+
+## ModelInfoSources.forPublishing property
+
+Return an error if project does not meet requirements of lexical-models repository
+
+**Signature:**
+
+```typescript
+forPublishing: boolean;
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfosources.kmpfilename.md b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.kmpfilename.md
new file mode 100644
index 00000000000..4d47a8ea9a4
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.kmpfilename.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoSources](./kmc-model-info.modelinfosources.md) > [kmpFileName](./kmc-model-info.modelinfosources.kmpfilename.md)
+
+## ModelInfoSources.kmpFileName property
+
+The compiled package filename and relative path (.kmp)
+
+**Signature:**
+
+```typescript
+kmpFileName: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfosources.kmpjsondata.md b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.kmpjsondata.md
new file mode 100644
index 00000000000..d0fbf7ebb7a
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.kmpjsondata.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoSources](./kmc-model-info.modelinfosources.md) > [kmpJsonData](./kmc-model-info.modelinfosources.kmpjsondata.md)
+
+## ModelInfoSources.kmpJsonData property
+
+The data from the .kps file, transformed to kmp.json
+
+**Signature:**
+
+```typescript
+kmpJsonData: KmpJsonFile.KmpJsonFile;
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfosources.kpsfilename.md b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.kpsfilename.md
new file mode 100644
index 00000000000..c80e6755afe
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.kpsfilename.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoSources](./kmc-model-info.modelinfosources.md) > [kpsFilename](./kmc-model-info.modelinfosources.kpsfilename.md)
+
+## ModelInfoSources.kpsFilename property
+
+The source package filename and relative path (.kps)
+
+**Signature:**
+
+```typescript
+kpsFilename: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfosources.lastcommitdate.md b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.lastcommitdate.md
new file mode 100644
index 00000000000..c78c7d5b9db
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.lastcommitdate.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoSources](./kmc-model-info.modelinfosources.md) > [lastCommitDate](./kmc-model-info.modelinfosources.lastcommitdate.md)
+
+## ModelInfoSources.lastCommitDate property
+
+Last modification date for files in the project folder 'YYYY-MM-DDThh:mm:ssZ'
+
+**Signature:**
+
+```typescript
+lastCommitDate?: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfosources.md b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.md
new file mode 100644
index 00000000000..c74ab3cafd6
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoSources](./kmc-model-info.modelinfosources.md)
+
+## ModelInfoSources class
+
+Description of sources and metadata required to build a .model\_info file
+
+**Signature:**
+
+```typescript
+export declare class ModelInfoSources
+```
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [forPublishing](./kmc-model-info.modelinfosources.forpublishing.md) | | boolean | Return an error if project does not meet requirements of lexical-models repository |
+| [kmpFileName](./kmc-model-info.modelinfosources.kmpfilename.md) | | string | The compiled package filename and relative path (.kmp) |
+| [kmpJsonData](./kmc-model-info.modelinfosources.kmpjsondata.md) | | KmpJsonFile.KmpJsonFile | The data from the .kps file, transformed to kmp.json |
+| [kpsFilename](./kmc-model-info.modelinfosources.kpsfilename.md) | | string | The source package filename and relative path (.kps) |
+| [lastCommitDate?](./kmc-model-info.modelinfosources.lastcommitdate.md) | | string | _(Optional)_ Last modification date for files in the project folder 'YYYY-MM-DDThh:mm:ssZ' |
+| [model\_id](./kmc-model-info.modelinfosources.model_id.md) | | string | The identifier for the model |
+| [modelFileName](./kmc-model-info.modelinfosources.modelfilename.md) | | string | The compiled model filename and relative path (.js) |
+| [sourcePath](./kmc-model-info.modelinfosources.sourcepath.md) | | string | The path in the keymanapp/lexical-models repo where this model may be found |
+
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfosources.model_id.md b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.model_id.md
new file mode 100644
index 00000000000..d0d40cde82b
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.model_id.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoSources](./kmc-model-info.modelinfosources.md) > [model\_id](./kmc-model-info.modelinfosources.model_id.md)
+
+## ModelInfoSources.model\_id property
+
+The identifier for the model
+
+**Signature:**
+
+```typescript
+model_id: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfosources.modelfilename.md b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.modelfilename.md
new file mode 100644
index 00000000000..2f4567e44fe
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.modelfilename.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoSources](./kmc-model-info.modelinfosources.md) > [modelFileName](./kmc-model-info.modelinfosources.modelfilename.md)
+
+## ModelInfoSources.modelFileName property
+
+The compiled model filename and relative path (.js)
+
+**Signature:**
+
+```typescript
+modelFileName: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfosources.sourcepath.md b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.sourcepath.md
new file mode 100644
index 00000000000..7df6ad03e57
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.sourcepath.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoSources](./kmc-model-info.modelinfosources.md) > [sourcePath](./kmc-model-info.modelinfosources.sourcepath.md)
+
+## ModelInfoSources.sourcePath property
+
+The path in the keymanapp/lexical-models repo where this model may be found
+
+**Signature:**
+
+```typescript
+sourcePath: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.casedwordformtokeyspec.md b/developer/docs/help/reference/api/kmc-model.casedwordformtokeyspec.md
new file mode 100644
index 00000000000..5403c6f0c5b
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.casedwordformtokeyspec.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [CasedWordformToKeySpec](./kmc-model.casedwordformtokeyspec.md)
+
+## CasedWordformToKeySpec type
+
+Simplifies input text to facilitate finding entries within a lexical model's lexicon, using the model's `applyCasing` function to assist in the keying process. 14.0
+
+**Signature:**
+
+```typescript
+export type CasedWordformToKeySpec = (term: string, applyCasing?: CasingFunction) => string;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.init.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.init.md
new file mode 100644
index 00000000000..56e6fc5c9bc
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.init.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelCompiler](./kmc-model.lexicalmodelcompiler.md) > [init](./kmc-model.lexicalmodelcompiler.init.md)
+
+## LexicalModelCompiler.init() method
+
+Initialize the compiler. There are currently no options specific to the lexical model compiler
+
+**Signature:**
+
+```typescript
+init(callbacks: CompilerCallbacks, _options: CompilerOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| callbacks | CompilerCallbacks | Callbacks for external interfaces, including message reporting and file io |
+| \_options | CompilerOptions | |
+
+**Returns:**
+
+Promise<boolean>
+
+always succeeds and returns true
+
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.md
new file mode 100644
index 00000000000..c7cab5b8f1c
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelCompiler](./kmc-model.lexicalmodelcompiler.md)
+
+## LexicalModelCompiler class
+
+Compiles a .model.ts file to a .model.js. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO.
+
+**Signature:**
+
+```typescript
+export declare class LexicalModelCompiler implements KeymanCompiler
+```
+**Implements:** KeymanCompiler
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [init(callbacks, \_options)](./kmc-model.lexicalmodelcompiler.init.md) | | Initialize the compiler. There are currently no options specific to the lexical model compiler |
+| [run(inputFilename, outputFilename)](./kmc-model.lexicalmodelcompiler.run.md) | | Compiles a .model.ts file to .model.js. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [LexicalModelCompiler.init()](./kmc-model.lexicalmodelcompiler.init.md) function to read any input files by disk. |
+| [write(artifacts)](./kmc-model.lexicalmodelcompiler.write.md) | | Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
- .model.js file - Javascript lexical model for web and touch platforms
|
+
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.run.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.run.md
new file mode 100644
index 00000000000..e96c22038a8
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.run.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelCompiler](./kmc-model.lexicalmodelcompiler.md) > [run](./kmc-model.lexicalmodelcompiler.run.md)
+
+## LexicalModelCompiler.run() method
+
+Compiles a .model.ts file to .model.js. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [LexicalModelCompiler.init()](./kmc-model.lexicalmodelcompiler.init.md) function to read any input files by disk.
+
+**Signature:**
+
+```typescript
+run(inputFilename: string, outputFilename?: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| inputFilename | string | |
+| outputFilename | string | _(Optional)_ |
+
+**Returns:**
+
+Promise<[LexicalModelCompilerResult](./kmc-model.lexicalmodelcompilerresult.md)>
+
+Binary artifacts on success, null on failure.
+
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.write.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.write.md
new file mode 100644
index 00000000000..c3fd2f6934c
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.write.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelCompiler](./kmc-model.lexicalmodelcompiler.md) > [write](./kmc-model.lexicalmodelcompiler.write.md)
+
+## LexicalModelCompiler.write() method
+
+Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
+
+- .model.js file - Javascript lexical model for web and touch platforms
+
+**Signature:**
+
+```typescript
+write(artifacts: LexicalModelCompilerArtifacts): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| artifacts | [LexicalModelCompilerArtifacts](./kmc-model.lexicalmodelcompilerartifacts.md) | object containing artifact binary data to write out |
+
+**Returns:**
+
+Promise<boolean>
+
+always returns true
+
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerartifacts.js.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerartifacts.js.md
new file mode 100644
index 00000000000..99f75d6a584
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerartifacts.js.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelCompilerArtifacts](./kmc-model.lexicalmodelcompilerartifacts.md) > [js](./kmc-model.lexicalmodelcompilerartifacts.js.md)
+
+## LexicalModelCompilerArtifacts.js property
+
+Javascript model filedata and filename - installable into KeymanWeb, Keyman mobile products
+
+**Signature:**
+
+```typescript
+js: KeymanCompilerArtifact;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerartifacts.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerartifacts.md
new file mode 100644
index 00000000000..903a3948a7d
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerartifacts.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelCompilerArtifacts](./kmc-model.lexicalmodelcompilerartifacts.md)
+
+## LexicalModelCompilerArtifacts interface
+
+Internal in-memory build artifacts from a successful compilation
+
+**Signature:**
+
+```typescript
+export interface LexicalModelCompilerArtifacts extends KeymanCompilerArtifacts
+```
+**Extends:** KeymanCompilerArtifacts
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [js](./kmc-model.lexicalmodelcompilerartifacts.js.md) | | KeymanCompilerArtifact | Javascript model filedata and filename - installable into KeymanWeb, Keyman mobile products |
+
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerresult.artifacts.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerresult.artifacts.md
new file mode 100644
index 00000000000..2d4259416db
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerresult.artifacts.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelCompilerResult](./kmc-model.lexicalmodelcompilerresult.md) > [artifacts](./kmc-model.lexicalmodelcompilerresult.artifacts.md)
+
+## LexicalModelCompilerResult.artifacts property
+
+Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [LexicalModelCompiler.write()](./kmc-model.lexicalmodelcompiler.write.md)
+
+**Signature:**
+
+```typescript
+artifacts: LexicalModelCompilerArtifacts;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerresult.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerresult.md
new file mode 100644
index 00000000000..eb541fc46a9
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerresult.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelCompilerResult](./kmc-model.lexicalmodelcompilerresult.md)
+
+## LexicalModelCompilerResult interface
+
+Build artifacts from the lexical model compiler
+
+**Signature:**
+
+```typescript
+export interface LexicalModelCompilerResult extends KeymanCompilerResult
+```
+**Extends:** KeymanCompilerResult
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [artifacts](./kmc-model.lexicalmodelcompilerresult.artifacts.md) | | [LexicalModelCompilerArtifacts](./kmc-model.lexicalmodelcompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [LexicalModelCompiler.write()](./kmc-model.lexicalmodelcompiler.write.md) |
+
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.applycasing.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.applycasing.md
new file mode 100644
index 00000000000..fb8f9c11005
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.applycasing.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelSource](./kmc-model.lexicalmodelsource.md) > [applyCasing](./kmc-model.lexicalmodelsource.applycasing.md)
+
+## LexicalModelSource.applyCasing property
+
+Specifies the casing rules for a language. Should implement three casing forms: - 'lower' -- a fully-lowercased version of the text appropriate for the language's use of the writing system. - 'upper' -- a fully-uppercased version of the text - 'initial' -- a version preserving the input casing aside from the initial character, which is uppercased (like with proper nouns and sentence-initial words in English sentences.)
+
+This is only utilized if `languageUsesCasing` is defined and set to `true`. 14.0
+
+**Signature:**
+
+```typescript
+readonly applyCasing?: CasingFunction;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.languageusescasing.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.languageusescasing.md
new file mode 100644
index 00000000000..51d899d4748
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.languageusescasing.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelSource](./kmc-model.lexicalmodelsource.md) > [languageUsesCasing](./kmc-model.lexicalmodelsource.languageusescasing.md)
+
+## LexicalModelSource.languageUsesCasing property
+
+When set to `true`, suggestions will attempt to match the case of the input text even if the lexicon entries use a different casing scheme due to search term keying effects. 14.0
+
+**Signature:**
+
+```typescript
+readonly languageUsesCasing?: boolean;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.md
new file mode 100644
index 00000000000..f3f88cfd6fe
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelSource](./kmc-model.lexicalmodelsource.md)
+
+## LexicalModelSource interface
+
+Base interface for a lexical model source definition
+
+**Signature:**
+
+```typescript
+export interface LexicalModelSource extends LexicalModelDeclaration
+```
+**Extends:** LexicalModelDeclaration
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [applyCasing?](./kmc-model.lexicalmodelsource.applycasing.md) | readonly
| CasingFunction | _(Optional)_ Specifies the casing rules for a language. Should implement three casing forms: - 'lower' -- a fully-lowercased version of the text appropriate for the language's use of the writing system. - 'upper' -- a fully-uppercased version of the text - 'initial' -- a version preserving the input casing aside from the initial character, which is uppercased (like with proper nouns and sentence-initial words in English sentences.)
This is only utilized if languageUsesCasing
is defined and set to true
. 14.0
|
+| [languageUsesCasing?](./kmc-model.lexicalmodelsource.languageusescasing.md) | readonly
| boolean | _(Optional)_ When set to true
, suggestions will attempt to match the case of the input text even if the lexicon entries use a different casing scheme due to search term keying effects. 14.0 |
+| [punctuation?](./kmc-model.lexicalmodelsource.punctuation.md) | readonly
| LexicalModelPunctuation | _(Optional)_ Punctuation and spacing suggested by the model. |
+| [rootClass?](./kmc-model.lexicalmodelsource.rootclass.md) | readonly
| string | _(Optional)_ The name of the type to instantiate (without parameters) as the base object for a custom predictive model. |
+| [searchTermToKey?](./kmc-model.lexicalmodelsource.searchtermtokey.md) | readonly
| [WordformToKeySpec](./kmc-model.wordformtokeyspec.md) | _(Optional)_ How to simplify words, to convert them into simplified search keys This often involves removing accents, lowercasing, etc. |
+| [sources](./kmc-model.lexicalmodelsource.sources.md) | readonly
| Array<string> | |
+| [wordBreaker?](./kmc-model.lexicalmodelsource.wordbreaker.md) | readonly
| [WordBreakerSpec](./kmc-model.wordbreakerspec.md) \| [SimpleWordBreakerSpec](./kmc-model.simplewordbreakerspec.md) | _(Optional)_ Which word breaker to use. Choose from:
- 'default' -- breaks according to Unicode UAX \#29 §4.1 Default Word Boundary Specification, which works well for \*most\* languages. - 'ascii' -- a very simple word breaker, for demonstration purposes only. - word breaking function -- provide your own function that breaks words. - class-based word-breaker - may be supported in the future.
|
+
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.punctuation.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.punctuation.md
new file mode 100644
index 00000000000..be062d49237
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.punctuation.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelSource](./kmc-model.lexicalmodelsource.md) > [punctuation](./kmc-model.lexicalmodelsource.punctuation.md)
+
+## LexicalModelSource.punctuation property
+
+Punctuation and spacing suggested by the model.
+
+**Signature:**
+
+```typescript
+readonly punctuation?: LexicalModelPunctuation;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.rootclass.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.rootclass.md
new file mode 100644
index 00000000000..2e4371eed16
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.rootclass.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelSource](./kmc-model.lexicalmodelsource.md) > [rootClass](./kmc-model.lexicalmodelsource.rootclass.md)
+
+## LexicalModelSource.rootClass property
+
+The name of the type to instantiate (without parameters) as the base object for a custom predictive model.
+
+**Signature:**
+
+```typescript
+readonly rootClass?: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.searchtermtokey.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.searchtermtokey.md
new file mode 100644
index 00000000000..ca9d0d8ae8e
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.searchtermtokey.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelSource](./kmc-model.lexicalmodelsource.md) > [searchTermToKey](./kmc-model.lexicalmodelsource.searchtermtokey.md)
+
+## LexicalModelSource.searchTermToKey property
+
+How to simplify words, to convert them into simplified search keys This often involves removing accents, lowercasing, etc.
+
+**Signature:**
+
+```typescript
+readonly searchTermToKey?: WordformToKeySpec;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.sources.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.sources.md
new file mode 100644
index 00000000000..6d11c0d372f
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.sources.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelSource](./kmc-model.lexicalmodelsource.md) > [sources](./kmc-model.lexicalmodelsource.sources.md)
+
+## LexicalModelSource.sources property
+
+**Signature:**
+
+```typescript
+readonly sources: Array;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.wordbreaker.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.wordbreaker.md
new file mode 100644
index 00000000000..c8f9053d263
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.wordbreaker.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelSource](./kmc-model.lexicalmodelsource.md) > [wordBreaker](./kmc-model.lexicalmodelsource.wordbreaker.md)
+
+## LexicalModelSource.wordBreaker property
+
+Which word breaker to use. Choose from:
+
+- 'default' -- breaks according to Unicode UAX \#29 §4.1 Default Word Boundary Specification, which works well for \*most\* languages. - 'ascii' -- a very simple word breaker, for demonstration purposes only. - word breaking function -- provide your own function that breaks words. - class-based word-breaker - may be supported in the future.
+
+**Signature:**
+
+```typescript
+readonly wordBreaker?: WordBreakerSpec | SimpleWordBreakerSpec;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.md b/developer/docs/help/reference/api/kmc-model.md
new file mode 100644
index 00000000000..fa0d8688436
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.md
@@ -0,0 +1,30 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md)
+
+## kmc-model package
+
+## Classes
+
+| Class | Description |
+| --- | --- |
+| [LexicalModelCompiler](./kmc-model.lexicalmodelcompiler.md) | Compiles a .model.ts file to a .model.js. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO. |
+
+## Interfaces
+
+| Interface | Description |
+| --- | --- |
+| [LexicalModelCompilerArtifacts](./kmc-model.lexicalmodelcompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation |
+| [LexicalModelCompilerResult](./kmc-model.lexicalmodelcompilerresult.md) | Build artifacts from the lexical model compiler |
+| [LexicalModelSource](./kmc-model.lexicalmodelsource.md) | Base interface for a lexical model source definition |
+| [WordBreakerSpec](./kmc-model.wordbreakerspec.md) | Keyman 14.0+ word breaker specification:
Can support all old word breaking specification, but can also be extended with options.
14.0
|
+
+## Type Aliases
+
+| Type Alias | Description |
+| --- | --- |
+| [CasedWordformToKeySpec](./kmc-model.casedwordformtokeyspec.md) | Simplifies input text to facilitate finding entries within a lexical model's lexicon, using the model's applyCasing
function to assist in the keying process. 14.0 |
+| [SimpleWordBreakerSpec](./kmc-model.simplewordbreakerspec.md) | Simplified word breaker specification.
11.0
|
+| [SimpleWordformToKeySpec](./kmc-model.simplewordformtokeyspec.md) | Simplifies input text to facilitate finding entries within a lexical model's lexicon. 11.0 |
+| [WordformToKeySpec](./kmc-model.wordformtokeyspec.md) | Simplifies input text to facilitate finding entries within a lexical model's lexicon. |
+
diff --git a/developer/docs/help/reference/api/kmc-model.simplewordbreakerspec.md b/developer/docs/help/reference/api/kmc-model.simplewordbreakerspec.md
new file mode 100644
index 00000000000..420a61218ea
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.simplewordbreakerspec.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [SimpleWordBreakerSpec](./kmc-model.simplewordbreakerspec.md)
+
+## SimpleWordBreakerSpec type
+
+Simplified word breaker specification.
+
+ 11.0
+
+**Signature:**
+
+```typescript
+export type SimpleWordBreakerSpec = 'default' | 'ascii' | WordBreakingFunction;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.simplewordformtokeyspec.md b/developer/docs/help/reference/api/kmc-model.simplewordformtokeyspec.md
new file mode 100644
index 00000000000..65e1cfb656d
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.simplewordformtokeyspec.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [SimpleWordformToKeySpec](./kmc-model.simplewordformtokeyspec.md)
+
+## SimpleWordformToKeySpec type
+
+Simplifies input text to facilitate finding entries within a lexical model's lexicon. 11.0
+
+**Signature:**
+
+```typescript
+export type SimpleWordformToKeySpec = (term: string) => string;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.wordbreakerspec.joinwordsat.md b/developer/docs/help/reference/api/kmc-model.wordbreakerspec.joinwordsat.md
new file mode 100644
index 00000000000..8a5c7d7e683
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.wordbreakerspec.joinwordsat.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [WordBreakerSpec](./kmc-model.wordbreakerspec.md) > [joinWordsAt](./kmc-model.wordbreakerspec.joinwordsat.md)
+
+## WordBreakerSpec.joinWordsAt property
+
+If present, joins words that were split by the word breaker together at the given strings. e.g.,
+
+joinWordsAt: \['-'\] // to keep hyphenated items together
+
+ 14.0
+
+**Signature:**
+
+```typescript
+readonly joinWordsAt?: string[];
+```
diff --git a/developer/docs/help/reference/api/kmc-model.wordbreakerspec.md b/developer/docs/help/reference/api/kmc-model.wordbreakerspec.md
new file mode 100644
index 00000000000..564f4ee8d57
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.wordbreakerspec.md
@@ -0,0 +1,26 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [WordBreakerSpec](./kmc-model.wordbreakerspec.md)
+
+## WordBreakerSpec interface
+
+Keyman 14.0+ word breaker specification:
+
+Can support all old word breaking specification, but can also be extended with options.
+
+ 14.0
+
+**Signature:**
+
+```typescript
+export interface WordBreakerSpec
+```
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [joinWordsAt?](./kmc-model.wordbreakerspec.joinwordsat.md) | readonly
| string\[\] | _(Optional)_ If present, joins words that were split by the word breaker together at the given strings. e.g.,
joinWordsAt: \['-'\] // to keep hyphenated items together
14.0
|
+| [overrideScriptDefaults?](./kmc-model.wordbreakerspec.overridescriptdefaults.md) | readonly
| OverrideScriptDefaults | _(Optional)_ Overrides word splitting behaviour for certain scripts. For example, specifing that spaces break words in certain South-East Asian scripts that otherwise do not use spaces.
14.0
|
+| [use](./kmc-model.wordbreakerspec.use.md) | readonly
| [SimpleWordBreakerSpec](./kmc-model.simplewordbreakerspec.md) | |
+
diff --git a/developer/docs/help/reference/api/kmc-model.wordbreakerspec.overridescriptdefaults.md b/developer/docs/help/reference/api/kmc-model.wordbreakerspec.overridescriptdefaults.md
new file mode 100644
index 00000000000..4c21721cc9c
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.wordbreakerspec.overridescriptdefaults.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [WordBreakerSpec](./kmc-model.wordbreakerspec.md) > [overrideScriptDefaults](./kmc-model.wordbreakerspec.overridescriptdefaults.md)
+
+## WordBreakerSpec.overrideScriptDefaults property
+
+Overrides word splitting behaviour for certain scripts. For example, specifing that spaces break words in certain South-East Asian scripts that otherwise do not use spaces.
+
+ 14.0
+
+**Signature:**
+
+```typescript
+readonly overrideScriptDefaults?: OverrideScriptDefaults;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.wordbreakerspec.use.md b/developer/docs/help/reference/api/kmc-model.wordbreakerspec.use.md
new file mode 100644
index 00000000000..5e3844226ad
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.wordbreakerspec.use.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [WordBreakerSpec](./kmc-model.wordbreakerspec.md) > [use](./kmc-model.wordbreakerspec.use.md)
+
+## WordBreakerSpec.use property
+
+**Signature:**
+
+```typescript
+readonly use: SimpleWordBreakerSpec;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.wordformtokeyspec.md b/developer/docs/help/reference/api/kmc-model.wordformtokeyspec.md
new file mode 100644
index 00000000000..5a2b4951a3e
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.wordformtokeyspec.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [WordformToKeySpec](./kmc-model.wordformtokeyspec.md)
+
+## WordformToKeySpec type
+
+Simplifies input text to facilitate finding entries within a lexical model's lexicon.
+
+**Signature:**
+
+```typescript
+export type WordformToKeySpec = SimpleWordformToKeySpec | CasedWordformToKeySpec;
+```
+**References:** [SimpleWordformToKeySpec](./kmc-model.simplewordformtokeyspec.md), [CasedWordformToKeySpec](./kmc-model.casedwordformtokeyspec.md)
+
diff --git a/developer/docs/help/reference/api/kmc-package.kmpcompiler.init.md b/developer/docs/help/reference/api/kmc-package.kmpcompiler.init.md
new file mode 100644
index 00000000000..c1010a494ee
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.kmpcompiler.init.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [KmpCompiler](./kmc-package.kmpcompiler.md) > [init](./kmc-package.kmpcompiler.init.md)
+
+## KmpCompiler.init() method
+
+Initialize the compiler. Copies options.
+
+**Signature:**
+
+```typescript
+init(callbacks: CompilerCallbacks, options: KmpCompilerOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| callbacks | CompilerCallbacks | Callbacks for external interfaces, including message reporting and file io |
+| options | [KmpCompilerOptions](./kmc-package.kmpcompileroptions.md) | Compiler options |
+
+**Returns:**
+
+Promise<boolean>
+
+false if initialization fails
+
diff --git a/developer/docs/help/reference/api/kmc-package.kmpcompiler.md b/developer/docs/help/reference/api/kmc-package.kmpcompiler.md
new file mode 100644
index 00000000000..f23b5025fa0
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.kmpcompiler.md
@@ -0,0 +1,29 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [KmpCompiler](./kmc-package.kmpcompiler.md)
+
+## KmpCompiler class
+
+Compiles a .kps file to a .kmp archive. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO.
+
+**Signature:**
+
+```typescript
+export declare class KmpCompiler implements KeymanCompiler
+```
+**Implements:** KeymanCompiler
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [normalizePath](./kmc-package.kmpcompiler.normalizepath.md) | readonly
| (path: string) => string | |
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [init(callbacks, options)](./kmc-package.kmpcompiler.init.md) | | Initialize the compiler. Copies options. |
+| [run(inputFilename, outputFilename)](./kmc-package.kmpcompiler.run.md) | | Compiles a .kps file to .kmp file. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [KmpCompiler.init()](./kmc-package.kmpcompiler.init.md) function to read any input files by disk. |
+| [write(artifacts)](./kmc-package.kmpcompiler.write.md) | | Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
- .kmp file - binary keyboard package used by Keyman on desktop and touch platforms
|
+
diff --git a/developer/docs/help/reference/api/kmc-package.kmpcompiler.normalizepath.md b/developer/docs/help/reference/api/kmc-package.kmpcompiler.normalizepath.md
new file mode 100644
index 00000000000..cf754363740
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.kmpcompiler.normalizepath.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [KmpCompiler](./kmc-package.kmpcompiler.md) > [normalizePath](./kmc-package.kmpcompiler.normalizepath.md)
+
+## KmpCompiler.normalizePath property
+
+**Signature:**
+
+```typescript
+readonly normalizePath: (path: string) => string;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.kmpcompiler.run.md b/developer/docs/help/reference/api/kmc-package.kmpcompiler.run.md
new file mode 100644
index 00000000000..4b584dd7979
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.kmpcompiler.run.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [KmpCompiler](./kmc-package.kmpcompiler.md) > [run](./kmc-package.kmpcompiler.run.md)
+
+## KmpCompiler.run() method
+
+Compiles a .kps file to .kmp file. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [KmpCompiler.init()](./kmc-package.kmpcompiler.init.md) function to read any input files by disk.
+
+**Signature:**
+
+```typescript
+run(inputFilename: string, outputFilename?: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| inputFilename | string | |
+| outputFilename | string | _(Optional)_ |
+
+**Returns:**
+
+Promise<[KmpCompilerResult](./kmc-package.kmpcompilerresult.md)>
+
+Binary artifacts on success, null on failure.
+
diff --git a/developer/docs/help/reference/api/kmc-package.kmpcompiler.write.md b/developer/docs/help/reference/api/kmc-package.kmpcompiler.write.md
new file mode 100644
index 00000000000..c4878571df5
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.kmpcompiler.write.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [KmpCompiler](./kmc-package.kmpcompiler.md) > [write](./kmc-package.kmpcompiler.write.md)
+
+## KmpCompiler.write() method
+
+Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
+
+- .kmp file - binary keyboard package used by Keyman on desktop and touch platforms
+
+**Signature:**
+
+```typescript
+write(artifacts: KmpCompilerArtifacts): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| artifacts | [KmpCompilerArtifacts](./kmc-package.kmpcompilerartifacts.md) | object containing artifact binary data to write out |
+
+**Returns:**
+
+Promise<boolean>
+
+true on success
+
diff --git a/developer/docs/help/reference/api/kmc-package.kmpcompilerartifacts.kmp.md b/developer/docs/help/reference/api/kmc-package.kmpcompilerartifacts.kmp.md
new file mode 100644
index 00000000000..612daab90f5
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.kmpcompilerartifacts.kmp.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [KmpCompilerArtifacts](./kmc-package.kmpcompilerartifacts.md) > [kmp](./kmc-package.kmpcompilerartifacts.kmp.md)
+
+## KmpCompilerArtifacts.kmp property
+
+Binary keyboard package filedata and filename - installable into Keyman desktop and mobile projects
+
+**Signature:**
+
+```typescript
+kmp: KeymanCompilerArtifact;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.kmpcompilerartifacts.md b/developer/docs/help/reference/api/kmc-package.kmpcompilerartifacts.md
new file mode 100644
index 00000000000..1a7f6f883ea
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.kmpcompilerartifacts.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [KmpCompilerArtifacts](./kmc-package.kmpcompilerartifacts.md)
+
+## KmpCompilerArtifacts interface
+
+Internal in-memory build artifacts from a successful compilation
+
+**Signature:**
+
+```typescript
+export interface KmpCompilerArtifacts extends KeymanCompilerArtifacts
+```
+**Extends:** KeymanCompilerArtifacts
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [kmp](./kmc-package.kmpcompilerartifacts.kmp.md) | | KeymanCompilerArtifact | Binary keyboard package filedata and filename - installable into Keyman desktop and mobile projects |
+
diff --git a/developer/docs/help/reference/api/kmc-package.kmpcompileroptions.md b/developer/docs/help/reference/api/kmc-package.kmpcompileroptions.md
new file mode 100644
index 00000000000..763eb040004
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.kmpcompileroptions.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [KmpCompilerOptions](./kmc-package.kmpcompileroptions.md)
+
+## KmpCompilerOptions interface
+
+Options for the .kps compiler
+
+**Signature:**
+
+```typescript
+export interface KmpCompilerOptions extends CompilerOptions
+```
+**Extends:** CompilerOptions
+
diff --git a/developer/docs/help/reference/api/kmc-package.kmpcompilerresult.artifacts.md b/developer/docs/help/reference/api/kmc-package.kmpcompilerresult.artifacts.md
new file mode 100644
index 00000000000..4905af4ac7c
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.kmpcompilerresult.artifacts.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [KmpCompilerResult](./kmc-package.kmpcompilerresult.md) > [artifacts](./kmc-package.kmpcompilerresult.artifacts.md)
+
+## KmpCompilerResult.artifacts property
+
+Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [KmpCompiler.write()](./kmc-package.kmpcompiler.write.md)
+
+**Signature:**
+
+```typescript
+artifacts: KmpCompilerArtifacts;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.kmpcompilerresult.md b/developer/docs/help/reference/api/kmc-package.kmpcompilerresult.md
new file mode 100644
index 00000000000..3371a01f9ef
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.kmpcompilerresult.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [KmpCompilerResult](./kmc-package.kmpcompilerresult.md)
+
+## KmpCompilerResult interface
+
+Build artifacts from the .kps compiler
+
+**Signature:**
+
+```typescript
+export interface KmpCompilerResult extends KeymanCompilerResult
+```
+**Extends:** KeymanCompilerResult
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [artifacts](./kmc-package.kmpcompilerresult.artifacts.md) | | [KmpCompilerArtifacts](./kmc-package.kmpcompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [KmpCompiler.write()](./kmc-package.kmpcompiler.write.md) |
+
diff --git a/developer/docs/help/reference/api/kmc-package.md b/developer/docs/help/reference/api/kmc-package.md
new file mode 100644
index 00000000000..53ab00c1907
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.md
@@ -0,0 +1,25 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md)
+
+## kmc-package package
+
+## Classes
+
+| Class | Description |
+| --- | --- |
+| [KmpCompiler](./kmc-package.kmpcompiler.md) | Compiles a .kps file to a .kmp archive. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO. |
+| [WindowsPackageInstallerCompiler](./kmc-package.windowspackageinstallercompiler.md) | Compiles a .kps file to a .exe installer. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO. |
+
+## Interfaces
+
+| Interface | Description |
+| --- | --- |
+| [KmpCompilerArtifacts](./kmc-package.kmpcompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation |
+| [KmpCompilerOptions](./kmc-package.kmpcompileroptions.md) | Options for the .kps compiler |
+| [KmpCompilerResult](./kmc-package.kmpcompilerresult.md) | Build artifacts from the .kps compiler |
+| [WindowsPackageInstallerCompilerArtifacts](./kmc-package.windowspackageinstallercompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation |
+| [WindowsPackageInstallerCompilerOptions](./kmc-package.windowspackageinstallercompileroptions.md) | Options for the .kps Windows package installer compiler |
+| [WindowsPackageInstallerCompilerResult](./kmc-package.windowspackageinstallercompilerresult.md) | Build artifacts from the .kps Windows package installer compiler |
+| [WindowsPackageInstallerSources](./kmc-package.windowspackageinstallersources.md) | Sources and metadata for the Windows package installer compiler |
+
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.init.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.init.md
new file mode 100644
index 00000000000..160d3265414
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.init.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerCompiler](./kmc-package.windowspackageinstallercompiler.md) > [init](./kmc-package.windowspackageinstallercompiler.init.md)
+
+## WindowsPackageInstallerCompiler.init() method
+
+Initialize the compiler. Copies options.
+
+**Signature:**
+
+```typescript
+init(callbacks: CompilerCallbacks, options: WindowsPackageInstallerCompilerOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| callbacks | CompilerCallbacks | Callbacks for external interfaces, including message reporting and file io |
+| options | [WindowsPackageInstallerCompilerOptions](./kmc-package.windowspackageinstallercompileroptions.md) | Compiler options |
+
+**Returns:**
+
+Promise<boolean>
+
+false if initialization fails
+
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.md
new file mode 100644
index 00000000000..c691fece706
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerCompiler](./kmc-package.windowspackageinstallercompiler.md)
+
+## WindowsPackageInstallerCompiler class
+
+Compiles a .kps file to a .exe installer. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO.
+
+**Signature:**
+
+```typescript
+export declare class WindowsPackageInstallerCompiler implements KeymanCompiler
+```
+**Implements:** KeymanCompiler
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [init(callbacks, options)](./kmc-package.windowspackageinstallercompiler.init.md) | | Initialize the compiler. Copies options. |
+| [run(inputFilename, outputFilename)](./kmc-package.windowspackageinstallercompiler.run.md) | | Compiles a .kps file to .exe Windows package installer file. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [WindowsPackageInstallerCompiler.init()](./kmc-package.windowspackageinstallercompiler.init.md) function to read any input files by disk. |
+| [write(artifacts)](./kmc-package.windowspackageinstallercompiler.write.md) | | Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
- .exe file - binary Windows package installer executable file
|
+
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.run.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.run.md
new file mode 100644
index 00000000000..1e79d3d9124
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.run.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerCompiler](./kmc-package.windowspackageinstallercompiler.md) > [run](./kmc-package.windowspackageinstallercompiler.run.md)
+
+## WindowsPackageInstallerCompiler.run() method
+
+Compiles a .kps file to .exe Windows package installer file. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [WindowsPackageInstallerCompiler.init()](./kmc-package.windowspackageinstallercompiler.init.md) function to read any input files by disk.
+
+**Signature:**
+
+```typescript
+run(inputFilename: string, outputFilename?: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| inputFilename | string | |
+| outputFilename | string | _(Optional)_ |
+
+**Returns:**
+
+Promise<[WindowsPackageInstallerCompilerResult](./kmc-package.windowspackageinstallercompilerresult.md)>
+
+Binary artifacts on success, null on failure.
+
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.write.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.write.md
new file mode 100644
index 00000000000..487696c5292
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.write.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerCompiler](./kmc-package.windowspackageinstallercompiler.md) > [write](./kmc-package.windowspackageinstallercompiler.write.md)
+
+## WindowsPackageInstallerCompiler.write() method
+
+Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
+
+- .exe file - binary Windows package installer executable file
+
+**Signature:**
+
+```typescript
+write(artifacts: WindowsPackageInstallerCompilerArtifacts): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| artifacts | [WindowsPackageInstallerCompilerArtifacts](./kmc-package.windowspackageinstallercompilerartifacts.md) | object containing artifact binary data to write out |
+
+**Returns:**
+
+Promise<boolean>
+
+true on success
+
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerartifacts.exe.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerartifacts.exe.md
new file mode 100644
index 00000000000..1fd3e8e522e
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerartifacts.exe.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerCompilerArtifacts](./kmc-package.windowspackageinstallercompilerartifacts.md) > [exe](./kmc-package.windowspackageinstallercompilerartifacts.exe.md)
+
+## WindowsPackageInstallerCompilerArtifacts.exe property
+
+Binary package installer filedata and filename - installable into Keyman desktop and mobile projects
+
+**Signature:**
+
+```typescript
+exe: KeymanCompilerArtifact;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerartifacts.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerartifacts.md
new file mode 100644
index 00000000000..2a21d3a3116
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerartifacts.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerCompilerArtifacts](./kmc-package.windowspackageinstallercompilerartifacts.md)
+
+## WindowsPackageInstallerCompilerArtifacts interface
+
+Internal in-memory build artifacts from a successful compilation
+
+**Signature:**
+
+```typescript
+export interface WindowsPackageInstallerCompilerArtifacts extends KeymanCompilerArtifacts
+```
+**Extends:** KeymanCompilerArtifacts
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [exe](./kmc-package.windowspackageinstallercompilerartifacts.exe.md) | | KeymanCompilerArtifact | Binary package installer filedata and filename - installable into Keyman desktop and mobile projects |
+
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompileroptions.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompileroptions.md
new file mode 100644
index 00000000000..b0aed5a5052
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompileroptions.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerCompilerOptions](./kmc-package.windowspackageinstallercompileroptions.md)
+
+## WindowsPackageInstallerCompilerOptions interface
+
+Options for the .kps Windows package installer compiler
+
+**Signature:**
+
+```typescript
+export interface WindowsPackageInstallerCompilerOptions extends KmpCompilerOptions
+```
+**Extends:** [KmpCompilerOptions](./kmc-package.kmpcompileroptions.md)
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [sources](./kmc-package.windowspackageinstallercompileroptions.sources.md) | | [WindowsPackageInstallerSources](./kmc-package.windowspackageinstallersources.md) | Sources and metadata for the Windows package installer compiler |
+
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompileroptions.sources.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompileroptions.sources.md
new file mode 100644
index 00000000000..de1b74dbd7a
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompileroptions.sources.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerCompilerOptions](./kmc-package.windowspackageinstallercompileroptions.md) > [sources](./kmc-package.windowspackageinstallercompileroptions.sources.md)
+
+## WindowsPackageInstallerCompilerOptions.sources property
+
+Sources and metadata for the Windows package installer compiler
+
+**Signature:**
+
+```typescript
+sources: WindowsPackageInstallerSources;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerresult.artifacts.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerresult.artifacts.md
new file mode 100644
index 00000000000..d1ea7e4a5a3
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerresult.artifacts.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerCompilerResult](./kmc-package.windowspackageinstallercompilerresult.md) > [artifacts](./kmc-package.windowspackageinstallercompilerresult.artifacts.md)
+
+## WindowsPackageInstallerCompilerResult.artifacts property
+
+Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [WindowsPackageInstallerCompiler.write()](./kmc-package.windowspackageinstallercompiler.write.md)
+
+**Signature:**
+
+```typescript
+artifacts: WindowsPackageInstallerCompilerArtifacts;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerresult.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerresult.md
new file mode 100644
index 00000000000..de9701b148c
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerresult.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerCompilerResult](./kmc-package.windowspackageinstallercompilerresult.md)
+
+## WindowsPackageInstallerCompilerResult interface
+
+Build artifacts from the .kps Windows package installer compiler
+
+**Signature:**
+
+```typescript
+export interface WindowsPackageInstallerCompilerResult extends KeymanCompilerResult
+```
+**Extends:** KeymanCompilerResult
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [artifacts](./kmc-package.windowspackageinstallercompilerresult.artifacts.md) | | [WindowsPackageInstallerCompilerArtifacts](./kmc-package.windowspackageinstallercompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [WindowsPackageInstallerCompiler.write()](./kmc-package.windowspackageinstallercompiler.write.md) |
+
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.appname.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.appname.md
new file mode 100644
index 00000000000..ca6069c3802
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.appname.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerSources](./kmc-package.windowspackageinstallersources.md) > [appName](./kmc-package.windowspackageinstallersources.appname.md)
+
+## WindowsPackageInstallerSources.appName property
+
+**Signature:**
+
+```typescript
+appName?: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.licensefilename.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.licensefilename.md
new file mode 100644
index 00000000000..d96a5a29757
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.licensefilename.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerSources](./kmc-package.windowspackageinstallersources.md) > [licenseFilename](./kmc-package.windowspackageinstallersources.licensefilename.md)
+
+## WindowsPackageInstallerSources.licenseFilename property
+
+**Signature:**
+
+```typescript
+licenseFilename: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.md
new file mode 100644
index 00000000000..0130266bad5
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.md
@@ -0,0 +1,26 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerSources](./kmc-package.windowspackageinstallersources.md)
+
+## WindowsPackageInstallerSources interface
+
+Sources and metadata for the Windows package installer compiler
+
+**Signature:**
+
+```typescript
+export interface WindowsPackageInstallerSources
+```
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [appName?](./kmc-package.windowspackageinstallersources.appname.md) | | string | _(Optional)_ |
+| [licenseFilename](./kmc-package.windowspackageinstallersources.licensefilename.md) | | string | |
+| [msiFilename](./kmc-package.windowspackageinstallersources.msifilename.md) | | string | |
+| [setupExeFilename](./kmc-package.windowspackageinstallersources.setupexefilename.md) | | string | |
+| [startDisabled](./kmc-package.windowspackageinstallersources.startdisabled.md) | | boolean | |
+| [startWithConfiguration](./kmc-package.windowspackageinstallersources.startwithconfiguration.md) | | boolean | |
+| [titleImageFilename?](./kmc-package.windowspackageinstallersources.titleimagefilename.md) | | string | _(Optional)_ |
+
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.msifilename.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.msifilename.md
new file mode 100644
index 00000000000..f6cc53efbc1
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.msifilename.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerSources](./kmc-package.windowspackageinstallersources.md) > [msiFilename](./kmc-package.windowspackageinstallersources.msifilename.md)
+
+## WindowsPackageInstallerSources.msiFilename property
+
+**Signature:**
+
+```typescript
+msiFilename: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.setupexefilename.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.setupexefilename.md
new file mode 100644
index 00000000000..8e00113ffa5
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.setupexefilename.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerSources](./kmc-package.windowspackageinstallersources.md) > [setupExeFilename](./kmc-package.windowspackageinstallersources.setupexefilename.md)
+
+## WindowsPackageInstallerSources.setupExeFilename property
+
+**Signature:**
+
+```typescript
+setupExeFilename: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.startdisabled.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.startdisabled.md
new file mode 100644
index 00000000000..89ba631c164
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.startdisabled.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerSources](./kmc-package.windowspackageinstallersources.md) > [startDisabled](./kmc-package.windowspackageinstallersources.startdisabled.md)
+
+## WindowsPackageInstallerSources.startDisabled property
+
+**Signature:**
+
+```typescript
+startDisabled: boolean;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.startwithconfiguration.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.startwithconfiguration.md
new file mode 100644
index 00000000000..6ec14bae7a6
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.startwithconfiguration.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerSources](./kmc-package.windowspackageinstallersources.md) > [startWithConfiguration](./kmc-package.windowspackageinstallersources.startwithconfiguration.md)
+
+## WindowsPackageInstallerSources.startWithConfiguration property
+
+**Signature:**
+
+```typescript
+startWithConfiguration: boolean;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.titleimagefilename.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.titleimagefilename.md
new file mode 100644
index 00000000000..c663ba44d06
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.titleimagefilename.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerSources](./kmc-package.windowspackageinstallersources.md) > [titleImageFilename](./kmc-package.windowspackageinstallersources.titleimagefilename.md)
+
+## WindowsPackageInstallerSources.titleImageFilename property
+
+**Signature:**
+
+```typescript
+titleImageFilename?: string;
+```
diff --git a/developer/docs/help/reference/bcp-47.md b/developer/docs/help/reference/bcp-47.md
new file mode 100644
index 00000000000..f5c8a49539e
--- /dev/null
+++ b/developer/docs/help/reference/bcp-47.md
@@ -0,0 +1,113 @@
+---
+title: BCP 47 language tags
+---
+
+Packages and lexical models may reference a [BCP 47 language tag][1]. A BCP
+47 tag is a standard way of referencing a language, used widely in the computer
+industry. Keyman uses BCP 47 tags in a number of areas, including:
+
+* providing language metadata about installed keyboards to the operating system
+ and to applications, so that, for example, spell checking has the correct
+ language, through [package metadata](../guides/distribute/)
+* [linking lexical models to keyboard layouts](../guides/lexical-models/)
+* facilitating searches for keyboards and lexical models on the Keyman site,
+ through a [.keyboard_info file](/developer/cloud/keyboard_info)
+
+A BCP 47 language tag is made up of multiple subtags. There are many possible
+subtags, but only three types are currently used in most places in Keyman
+Developer:
+
+* [language subtag](#toc-the-language-subtag)
+* [script subtag](#toc-the-script-subtag)
+* [region subtag](#toc-the-region-subtag)
+
+BCP 47 tags are case insensitive, but there are conventions for casing which you
+should use for readability; see the subtag descriptions for details.
+
+The following are all examples of valid BCP 47 tags:
+
+* `en`: English
+* `en-US`: English, in United States
+* `km-Khmr-KH`: Khmer, written in the Khmer script, in Cambodia
+* `km-fonipa`: Khmer, transcribed in IPA
+
+### The language subtag
+
+The only required option is the Language subtag, which is an [ISO 639-1][2] or
+[ISO 639-3][3] code.
+
+ISO 639-1 tags are a two-letter code. ISO 639-3 tags are a three-letter code.
+First, try to find your language on the list of two-letter ISO 639-1 codes.
+[This Wikipedia page][4] lists all of the two-letter codes.
+
+If you can't find a two-letter code, you'll need to find the closest
+three-letter code. You can use [Glottolog][5] to search for your language, and
+it will give you an appropriate code. In this example, I searched Glottolog for
+“[Saanich][6]” (name of the First Nations that speak SENĆOŦEN) and found `str`
+as the code for all Straits Salish languages.
+
+The Language subtag is conventionally written in lower case.
+
+The next two subtags are **optional**, however, they allow you to be more
+specific about your language.
+
+### The script subtag
+
+The Script subtag allows you to specify the writing system used in your language
+model or keyboard. If your language only uses one writing system, omit the
+Script subtag.
+
+Otherwise, in cases where a language can be written in many different writing
+systems, you can choose the four letter [ISO 15924][7] script tag that your
+keyboard or lexical model produces.
+
+For example, Plains Cree can either be written in _standard Roman orthography_,
+a **Latin** derived script, or it can be written in _syllabics_, which is part
+of the **Canadian Aboriginal syllabics** family of writing systems. If I wrote a
+keyboard or lexical model that produced syllabics, I would choose `Cans`, as
+that is the **ISO 15924** tag for Canadian Aboriginal syllabics.
+
+The Script subtag is conventionally written in title case - first letter
+capitalized.
+
+### The region subtag
+
+The Region subtag allows you to specify the region your language or dialect is
+spoken in. If your language is only spoken in one region, omit the Region
+subtag.
+
+Otherwise, some languages vary between different regions and countries. In our
+example, SENĆOŦEN describes the language that covers entire W̱SÁNEĆ region, so
+this field may be left blank.
+
+However, large languages, like English, Spanish, or French have quite different
+vocabulary and even different grammatical rules from region to region and
+country to country. For example, the variety of Spanish spoken in Spain
+regularly uses words that are uncommon or even vulgar in both in Mexico, and in
+Latin America. Additionally, regions may have vocabulary that doesn't exist in
+the other regions where the language is spoken.
+
+If I were working with a language specific to one country, I would use the [ISO
+3166-1 alpha-2][8] country code for the region subtag. For example, `ES` for
+Spain or `MX` for Mexico.
+
+However, if I were working with Latin American Spanish (a group of countries), I
+would need to specify Latin America's [UN M49][9] region code. For Latin
+America, its code is `419`. My lexical model would not suggest words that are
+common in Spain, but vulgar in Latin America, however it would predict words
+like "pupupsas" and "chuchitos", which are words that are uncommon in both Spain
+and Mexico.
+
+Another common UN M49 region code is `001` for the whole world.
+
+Alphabetic region subtags are conventionally written in upper case.
+
+[1]: https://en.wikipedia.org/wiki/IETF_language_tag
+[2]: https://en.wikipedia.org/wiki/ISO_639-1
+[3]: https://en.wikipedia.org/wiki/ISO_639-3
+[4]: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+[5]: https://glottolog.org/glottolog/language
+[6]: https://glottolog.org/resource/languoid/id/saan1246
+[7]: https://en.wikipedia.org/wiki/ISO_15924
+[8]: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
+[9]: https://en.wikipedia.org/wiki/UN_M49
diff --git a/developer/docs/help/reference/editor-themes.md b/developer/docs/help/reference/editor-themes.md
new file mode 100644
index 00000000000..3a624c923fc
--- /dev/null
+++ b/developer/docs/help/reference/editor-themes.md
@@ -0,0 +1,46 @@
+---
+title: Custom Editor Themes
+---
+
+Keyman Developer embeds the Monaco open source text editor. The
+highlighting and styles for the text editor are customisable with a JSON
+file configured in the [Options dialog](../context/options). The JSON
+file must be a valid [Monaco theme](https://microsoft.github.io/monaco-editor/playground.html#customizing-the-appearence-tokens-and-colors)
+(passed as second parameter to `monaco.editor.defineTheme`).
+
+## Example
+
+```js
+{
+ "base": "vs-dark",
+ "inherit": true,
+ "rules": [
+ { "token": "comment", "foreground": "ffa500", "background": "303030", "fontStyle": "italic underline" },
+ { "token": "comment.js", "foreground": "008800", "fontStyle": "bold" },
+ { "token": "comment.css", "foreground": "0000ff", "fontStyle": "bold", "inherit": false, "background": "808080" }
+ ]
+}
+```
+
+## Keyman keyboard language tokens
+
+The Keyman keyboard language highlighting uses the following token
+names:
+
+| Token | Source Element |
+|----------------|-------------------------------------------------|
+| annotation | System store names |
+| bracket | |
+| comment | Comments |
+| identifier | Store and Group names |
+| invalid | Invalid code |
+| keyword | Keywords such as `begin`, `any` |
+| number | Decimal character codes, deprecated, e.g. `d65` |
+| number.hex | Unicode character codes, e.g. `U+1234` |
+| number.octal | Character codes expressed in octal |
+| operator | `+`, `>` and other operators |
+| string | Strings |
+| string.invalid | Unterminated strings |
+| string.quote | `'` and `"` characters |
+| tag | Virtual keys |
+| white | |
\ No newline at end of file
diff --git a/developer/docs/help/reference/errors/index.md b/developer/docs/help/reference/errors/index.md
new file mode 100644
index 00000000000..c5e8acc3680
--- /dev/null
+++ b/developer/docs/help/reference/errors/index.md
@@ -0,0 +1,4 @@
+---
+title: Errors
+redirect: ../messages
+---
diff --git a/developer/docs/help/reference/file-layout.md b/developer/docs/help/reference/file-layout.md
new file mode 100644
index 00000000000..e466d7c05b0
--- /dev/null
+++ b/developer/docs/help/reference/file-layout.md
@@ -0,0 +1,131 @@
+---
+title: Keyman Developer structure for standard keyboard source file folders
+---
+
+Keyman Developer generates keyboard project files in a standard layout. [Keyman
+Cloud](/developer/keyboards/) uses this file layout. It is highly recommended to
+adhere to this file layout.
+
+## About the source files
+
+When you create and build a project, you'll end up with the following structure.
+Some files may be not be present, depending on the targets you specify.
+
+```plain
+sample
+│ HISTORY.md
+│ LICENSE.md
+│ README.md
+│ sample.kpj
+│ sample.kpj.user
+│
+├───build
+| sample.keyboard_info
+│ sample.kmp
+│ sample.kmx
+│ sample.kvk
+│ sample.js
+│
+└───source
+ readme.htm
+ sample.keyman-touch-layout
+ sample.kmn
+ sample.kps
+ sample.kvks
+ welcome.htm
+```
+
+Now that probably seems like a whole lot of files! But each file has a purpose.
+
+### Root folder
+
+`sample.kpj`
+: The main project file. This contains references to all the components:
+ keyboards, models, and packages.
+
+`sample.kpj.user`
+
+: A user preference file. You can safely delete it at any time (you'll lose some
+ remembered settings from Keyman Developer, but nothing consequential) and it
+ should not be shared in a git repository.
+
+Some of these files are metadata files: information about your keyboard project
+that will help you and others maintain your keyboard project in the future.
+
+For example, you may wish to make your keyboard project into a git repository
+and push it an online public git host such as GitHub or GitLab.
+
+`README.md`
+: Provides an introduction to the keyboard project when others stumble across
+ it. This file is in [Markdown](https://commonmark.org) format.
+
+`LICENSE.md`
+: Explains the rights that you give to others (by default, it will be the MIT
+ license -- which is what we require for Keyman Cloud -- but you can change
+ that to any license you wish). This file is in
+ [Markdown](https://commonmark.org) format.
+
+`HISTORY.md`
+: A place to record changes you make to the project over time. This file is in
+[Markdown](https://commonmark.org) format.
+
+* Note: Keyman Developer 16 included a .keyboard_info file in this folder. This
+ file is no longer required, as the required metadata can be generated from
+ the keyboard source files instead.
+
+### Source folder
+
+The files in the source folder are more fully described in the Keyman Developer
+[user guide](../..).
+
+[`source/sample.kmn`](/developer/language)
+: Your keyboard source. It will compile to a `.kmx` (desktop targets) or a `.js`
+ (web / touch targets).
+
+[`source/sample.kps`](../reference/file-types/kps)
+: The package source, which is used to create a compressed `.kmp` archive of the
+ files needed for distribution.
+
+`source/readme.htm`
+: Introductory web page for end users of your keyboard, shown before they
+ install it, so they know what it is used for.
+
+`source/welcome.htm`
+: A web page which describes how to use your keyboard, ideally with examples.
+
+[`source/sample.keyman-touch-layout`](../reference/file-types/keyman-touch-layout)
+: This touch layout file description is in JSON format and most easily visually
+ edited with the Keyman Developer touch layout editor. This file is optional;
+ remove the reference from the keyboard source if you don't wish to use it.
+
+[`source/sample.kvks`](../reference/file-types/kvks):
+: The on screen keyboard template for desktop platforms. It is in XML format and
+ most easily visually edited with the Keyman Developer on screen keyboard
+ editor. This file is optional; remove the reference from the keyboard source
+ if you don't wish to use it.
+
+### Build folder
+
+All of the files in the build folder are generated by the `kmc` compiler. None
+of these files should be included in a git repository.
+
+`build/sample.keyboard_info`
+: A [metadata file](/developer/cloud/keyboard_info) detailing the
+ keyboard's origin, version, requirements, and capabilities.
+ This file is built automatically by the kmc compiler. Please see
+ [.keyboard_info specification](/developer/cloud/keyboard_info) for more
+ details.
+
+`build/sample.kmp`
+: The installable package file -- installable in all Keyman end user products
+
+`build/sample.js`
+: The keyboard compiled to Javascript for use with KeymanWeb
+
+`build/sample.kvk`
+: The compiled on screen keyboard. This intermediate file should not be
+ distributed; it is included in the compiled package.
+
+`build/sample.kmx`
+: The compiled keyboard. This intermediate file should not be distributed; it is
+ included in the compiled package.
diff --git a/developer/docs/help/reference/file-types/ico.md b/developer/docs/help/reference/file-types/ico.md
new file mode 100644
index 00000000000..33331318781
--- /dev/null
+++ b/developer/docs/help/reference/file-types/ico.md
@@ -0,0 +1,20 @@
+---
+title: ICO files
+---
+
+Used by:
+: Keyman Desktop ,
+ Keyman Developer .
+
+Description:
+: An .ICO file is a standard Windows
+ [icon](https://en.wikipedia.org/wiki/ICO_(file_format)) file.
+
+Details:
+: An .ICO file is an icon which is used in the user interface to
+ indicate that the keyboard is active. See the [bitmap
+ store](../../../language/reference/bitmap)
+
+Distributed with keyboard:
+: An .ICO file is associated with a specific keyboard. A generic
+ keyboard icon will be displayed if no icon is specified.
diff --git a/developer/docs/help/reference/file-types/index.md b/developer/docs/help/reference/file-types/index.md
new file mode 100644
index 00000000000..a4a47591bf4
--- /dev/null
+++ b/developer/docs/help/reference/file-types/index.md
@@ -0,0 +1,45 @@
+---
+title: File Types
+---
+
+[ICO files](ico)
+: Keyboard icon files (binary file format)
+
+[KMN files](kmn)
+: Keyboard source files (text file format)
+
+[KMP files](kmp)
+: Keyboard package files (zip file format)
+
+[KMX files](kmx)
+: Keyboard files (binary file format)
+
+[KPJ files](kpj)
+: Keyman Developer Project files (XML file format)
+
+[KPS files](kps)
+: Keyboard package source files (XML file format)
+
+[KVK files](kvk)
+: Visual keyboard files (binary file format)
+
+[KVKS files](kvks)
+: Visual keyboard source files (XML file format)
+
+[MODEL.JS files](model-js)
+: Lexical model files (text file format)
+
+[MODEL.TS files](model-ts)
+: Lexical model definition files (text file format)
+
+[TSV files](tsv)
+: Word list source files (tab-separated values file format)
+
+[TTF files](ttf)
+: TrueType font files (binary file format)
+
+[keyman-touch-layout files](keyman-touch-layout)
+: Keyman touch layout source files (JSON file format)
+
+[XML files](xml)
+: LDML keyboard source files (XML file format)
diff --git a/developer/docs/help/reference/file-types/keyman-touch-layout.md b/developer/docs/help/reference/file-types/keyman-touch-layout.md
new file mode 100644
index 00000000000..8fa7312808e
--- /dev/null
+++ b/developer/docs/help/reference/file-types/keyman-touch-layout.md
@@ -0,0 +1,847 @@
+---
+title: keyman-touch-layout files
+---
+
+Used by:
+: Keyman Developer .
+
+Description:
+: A .keyman-touch-layout file is a JSON format file that describes a
+ keyboard layout for touch devices.
+
+Details:
+: Referenced by a Keyman keyboard ([.KMN](kmn)) and compiles into the
+ target keyboard .js file.
+
+Distributed with keyboard:
+: This is a keyboard development file and should not be distributed
+ with your package.
+
+---
+
+## Sample JSON
+
+This cut-down sample shows just a single key, with the key cap "ឆ", and a single longpress key under it with the key cap "ឈ".
+
+```json
+{
+ "phone": {
+ "displayUnderlying": false,
+ "font": "Khmer Busra Kbd",
+ "fontsize": "0.8em",
+ "layer": [ {
+ "id": "default",
+ "row": [ {
+ "id": 1,
+ "key": [ {
+ "id": "K_Q", "text": "ឆ",
+ "sk": [ { "text": "ឈ", "id": "K_Q", "layer": "shift" } ]
+ } ]
+ } ]
+ } ]
+ }
+}
+```
+
+
+
+
+
+## Key properties
+
+For each visual key, the appearance and behaviour is determined by a number of
+properties:
+
+### Key code
+
+Each key must be given an identifying key code which is unique to the key layer.
+Key codes by and large correspond to the virtual key codes used when creating a
+keyboard program for a desktop keyboard, and should start with `K_`, for keys
+mapped to standard Keyman virtual key names, e.g. `K_HYPHEN`, and `T_` or `U_`
+for user-defined names, e.g. `T_ZZZ`. If keyboard rules exist matching the key
+code in context, then the output from the key will be determined by the
+processing of those rules. It is usually best to include explicit rules to
+manage the output from each key, but if no rules matching the key code are
+included in the keyboard program, and the key code matches the pattern
+`U_xxxx[_yyyy...]` (where `xxxx` and
+`yyyy` are 4 to 6-digit hex strings), then the Unicode characters
+`U+xxxx` and `U+yyyy` will be output. As of Keyman 15, you
+can use more than one Unicode character value in the id (earlier versions
+permitted only one). The key code is always required, and a default code will
+usually be generated automatically by Keyman Developer.
+
+- `K_xxxx` is used for a standard Keyman Desktop key name, e.g.
+ `K_W`, `K_ENTER`. You cannot make up your own `K_xxxx` names.
+ Many of the `K_` ids have overloaded output behaviour, for instance, if no
+ rule is matched for `K_W`, Keyman will output 'w' when it is touched. The
+ standard key names are listed in [Virtual Keys and Virtual Character
+ Keys](/developer/language/guide/virtual-keys "Virtual Keys and Virtual
+Character Keys"). Typically, you would use only the "common" virtual key
+ codes.
+
+- `T_xxxx` is used for any user defined names, e.g. `T_SCHWA`. If you wanted
+ to use it, `T_ENTER` would also be valid. If no rule matches it, the key
+ will have no output behaviour.
+
+- `U_####[_####]` is used as a shortcut for a key that will output those
+ Unicode values, if no rule matches it. This is similar to the overloaded
+ behaviour for `K_` ids. Thus `####` must be valid Unicode characters. E.g.
+ `U_0259` would generate a schwa if no rule matches. It is still valid to
+ have a rule such as `+ [U_0259] > ...`
+
+As noted above, some `K_xxxx` codes emit characters, if no rule is defined.
+There are also some codes which have special functions:
+
+
+
+
+ Identifier
+ Meaning
+
+
+
+
+ `K_ENTER`
+ Submit a form, or add a new line (multi-line); the key action may vary depending on the situation.
+
+
+ `K_BKSP`
+ Delete back a single character. This key, if held down, will repeat. It is the only key code which triggers
+ repeat behavior.
+
+
+ `K_LOPT`
+ Open the language menu (aka Globe key).
+
+
+ `K_ROPT`
+ Hide the on screen keyboard.
+
+
+ `K_TAB`, `K_TABBACK`, `K_TABFWD`
+ Move to next or previous element in a form. Note that these key functions are normally
+ implemented outside the touch layout, so should not typically be used. `K_TAB` will go to previous
+ element if used with the `shift` modifier.
+
+
+
+
+Any key can be used to switch keyboard layers (see
+[`nextlayer`](#toc-nextlayer)), but the following layer-switching key codes have
+been added for switching to some commonly used secondary layers. Note that these
+keys have no specific meaning; you must still set the `nextlayer` property on
+the key.
+
+
+
+
+ Identifier
+ Meaning
+
+
+
+
+ `K_NUMERALS`
+ Switch to a numeric layer
+
+
+ `K_SYMBOLS`
+ Switch to a symbol layer
+
+
+ `K_CURRENCIES`
+ Switch to a currency layer
+
+
+ `K_SHIFTED`
+ Switch to a shift layer
+
+
+ `K_ALTGR`
+ Switch to a right-alt layer (desktop compatibility)
+
+
+
+
+### Key text
+
+The key text is simply the character (or characters) that you want to appear on
+the key cap. This will usually be the same as the characters generated when the
+key is touched, unless contextual rules are used to generate output according to
+a multi-key sequence, as will be true for the GFF Amharic keyboard. Unicode
+characters can be specified either as a string using a target font or using the
+standard hex notation `\uxxxx`. This may be sometimes more convenient, for
+example, for characters from an uninstalled font, or for diacritic characters
+that do not render well alone.
+
+A number of special text labels are recognized as identifying special purpose
+keys, such as Shift, Backspace, Enter, etc., for which icons are more
+appropriately used than a text label. A special font including these icons is
+included with Keyman and automatically embedded and used in any web page using
+Keyman. The list of icons in the font will probably be extended in future, but
+for now the following special labels are recognized:
+
+
+
+
+ Text String
+ Key Cap
+ Key Purpose
+
+
+
+
+ `*Shift*`
+
+ Select Shift layer (inactive). Use on the Shift key to indicate that it switches to the shift layer.
+
+
+ `*Shifted*`
+
+ Select Shift layer (active). Use on the Shift key on the shift layer to switch back to the default layer.
+
+
+ `*ShiftLock*`
+
+ Switch to Caps layer (inactive). Not commonly used; generally double-tap on Shift key is used to access the
+ caps layer.
+
+
+ `*ShiftedLock*`
+
+ Switch to Caps layer (active). Use on the Shift key on the caps layer to switch back to the default layer.
+
+
+
+ `*Enter*`
+ or
+ Return or Enter key (shape determined by writing system direction)
+
+
+ `*LTREnter*`
+
+ Return or Enter key (left-to-right script shape)
+
+
+ `*RTLEnter*`
+
+ Return or Enter key (right-to-left script shape)
+
+
+ `*BkSp*`
+ or
+ Backspace key (shape determined by writing system direction)
+
+
+ `*LTRBkSp*`
+
+ Backspace key (left-to-right script shape)
+
+
+ `*RTLBkSp*`
+
+ Backspace key (right-to-left script shape)
+
+
+ `*Menu*`
+
+ Globe key; display the language menu. Use on the `K_LOPT` key.
+
+
+ `*Hide*`
+
+ Hide the on screen keyboard. Use on the `K_ROPT` key.
+
+
+ `*ABC*`
+
+ Select alphabetic layer (Uppercase)
+
+
+ `*abc*`
+
+ Select alphabetic layer (Lowercase)
+
+
+ `*123*`
+
+ Select the numeric layer
+
+
+ `*Symbol*`
+
+ Select the symbol layer
+
+
+ `*Currency*`
+
+ Select the currency symbol layer
+
+
+ `*ZWNJ*`
+ (iOS) or (Android)
+ Zero Width Non Joiner (shape determined by current platform)
+
+
+ `*ZWNJiOS*`
+
+ Zero Width Non Joiner (iOS style shape)
+
+
+ `*ZWNJAndroid*`
+
+ Zero Width Non Joiner (Android style shape)
+
+
+ `*ZWNJGeneric*`
+
+ Zero Width Non Joiner (not platform-specific)
+
+
+ `*Sp*`
+
+ Regular space
+
+
+ `*NBSp*`
+
+ No-Break Space
+
+
+ `*NarNBSp*`
+
+ Narrow No-Break Space
+
+
+ `*EnQ*`
+
+ En Quad
+
+
+ `*EmQ*`
+
+ Em Quad
+
+
+ `*EnSp*`
+
+ En Space
+
+
+ `*EmSp*`
+
+ Em Space
+
+
+ `*PunctSp*`
+
+ Punctuation Space
+
+
+ `*ThSp*`
+
+ Thin Space
+
+
+ `*HSp*`
+
+ Hair Space
+
+
+ `*ZWSp*`
+
+ Zero Width Space
+
+
+ `*ZWJ*`
+
+ Zero Width Joiner
+
+
+ `*WJ*`
+
+ Word Joiner
+
+
+ `*CGJ*`
+
+ Combining Grapheme Joiner
+
+
+ `*LTRM*`
+
+ Left-to-right Mark
+
+
+ `*RTLM*`
+
+ Right-to-left Mark
+
+
+ `*SH*`
+
+ Soft Hyphen
+
+
+ `*HTab*`
+
+ Horizontal Tabulation
+
+
+
+
+
+The following additional symbols are also available, but intended for working
+with legacy desktop layouts, and not recommended for general use:
+
+
+
+
+ Text String
+ Key Cap
+ Key Purpose
+
+
+
+
+ `*Tab*`
+
+ Move to next input element in tab order
+
+
+ `*TabLeft*`
+
+ Move to previous input element in tab order
+
+
+ `*Caps*`
+
+ Select caps layer (legacy)
+
+
+ `*AltGr*`
+
+ Select AltGr (Right-Alt) layer (desktop layout compatibility)
+
+
+ `*Alt*`
+
+ Select Alt layer (desktop layout compatibility)
+
+
+ `*Ctrl*`
+
+ Select Ctrl layer (desktop layout compatibility)
+
+
+ `*LAlt*`
+
+ Select Left-Alt layer (desktop layout compatibility)
+
+
+ `*RAlt*`
+
+ Select Right-Alt layer (desktop layout compatibility)
+
+
+ `*LCtrl*`
+
+ Select Left-Ctrl layer (desktop layout compatibility)
+
+
+ `*RCtrl*`
+
+ Select Right-Ctrl layer (desktop layout compatibility)
+
+
+ `*LAltCtrl*`
+
+ Select Left-Alt-Ctrl layer (desktop layout compatibility)
+
+
+ `*RAltCtrl*`
+
+ Select Right-Alt-Ctrl layer (desktop layout compatibility)
+
+
+ `*LAltCtrlShift*`
+
+ Select Left-Alt-Ctrl-Shift layer (desktop layout compatibility)
+
+
+ `*RAltCtrlShift*`
+
+ Select Right-Alt-Ctrl-Shift layer (desktop layout compatibility)
+
+
+ `*AltShift*`
+
+ Select Alt-Shift layer (desktop layout compatibility)
+
+
+ `*CtrlShift*`
+
+ Select Ctrl-Shift layer (desktop layout compatibility)
+
+
+ `*AltCtrlShift*`
+
+ Select Alt-Ctrl-Shift layer (desktop layout compatibility)
+
+
+ `*LAltShift*`
+
+ Select Left-Alt-Shift layer (desktop layout compatibility)
+
+
+ `*RAltShift*`
+
+ Select Right-Alt-Shift layer (desktop layout compatibility)
+
+
+ `*LCtrlShift*`
+
+ Select Left-Ctrl-Shift layer (desktop layout compatibility)
+
+
+ `*RCtrlShift*`
+
+ Select Right-Ctrl-Shift layer (desktop layout compatibility)
+
+
+
+
+### Key type
+
+The general appearance of each key is determined by the key type, which is
+selected (in Keyman Developer) from a drop-down list. While generally behavior
+is not impacted by the key type, Spacer keys cannot be selected.
+
+
+
+
+ Key Type
+ Value
+ Meaning
+
+
+
+
+ Default
+ `0`
+ Any normal key that emits a character
+
+
+ Special
+ `1`
+ The frame keys such as Shift, Enter, BkSp.
+
+
+ Special (active)
+ `2`
+ A frame key which is currently active, such as the Shift key on the shift layer.
+
+
+ Deadkey
+ `8`
+ Does not impact behavior, but colors the key differently to indicate it has a special function, such as a
+ desktop-style deadkey.
+
+
+ Blank
+ `9`
+ A blank key, which may be used to maintain a layout shape. Usually colored differently. Does not impact
+ behavior.
+
+
+ Spacer
+ `10`
+ Does not render the key, but leaves a same-sized gap in its place. The key cannot be selected.
+
+
+
+
+The colour, shading and borders of each key type is actually set by a style
+sheet which can be customized by the page developer.
+
+### font-family
+
+If a different font is required for a particular key text, the `font-family`
+name can be specified. The font used to display icons for the special keys (as
+mentioned above) does not need to be specified, as it will be automatically
+applied to a key that uses any of the special key text labels.
+
+### font-size
+
+If a particular key cap text requires a different font size from the default for
+the layout, it should be specified in em units. This can be helpful if a the key
+text is either an unusually large character or, alternatively, a word or string
+of several characters that would not normally fit on the key.
+
+### width
+
+The layout is scaled to fit the widest row of keys in the device width, assuming
+a default key width of 100 units. Keys that are to be wider or narrower than the
+default width should have width specified as a percentage of the default width.
+For any key row that is narrower than the widest row, the width of the last key
+in the row will be automatically increased to align the right hand side of the
+key with the key with the right edge of the keyboard. However, where this is not
+wanted, a "spacer" key can be inserted to leave a visible space instead. As
+shown in the above layouts, where the spacer key appears on the designer screen
+as a narrow key, but will not be visible in actual use.
+
+### pad
+
+Padding to the left of each key can be adjusted, and specified as a percentage
+of the default key width. If not specified, a standard padding of 5% of the key
+width is used between adjacent keys.
+
+### layer
+
+To simplify correspondence with desktop keyboards and avoid the need for using a
+separate keyboard mapping program, touch layout keys can specify a desktop
+keyboard layer that the keystroke should be interpreted as coming from. Layer
+names of `shift`, `ctrl`, `alt`, `ctrlshift`, `altshift`, `ctrlalt` and
+`ctrlaltshift` can be used to simulate use of the appropriate modifier keys when
+processing rules.
+
+### nextlayer
+
+The virtual keys `K_SHIFT`, `K_CONTROL`, `K_MENU`, etc. are normally used to
+switch to another key layer, which is implied by the key code. The left and
+right variants of those key codes, and also additional layer-switching keys
+mentioned above (`K_NUMERALS`, `K_SYMBOLS`, `K_CURRENCIES`, `K_ALTGR`) can also
+be used to automatically switch to the appropriate key layer instead of
+outputting a character. However, it is sometimes useful for a key to output a
+character first, then switch to a new layer, for example, switching back to the
+default keyboard layer after a punctuation key on a secondary layer had been
+used. Specifying the `nextlayer` for a key allows a different key layer to be
+selected automatically following the output of the key. Of course, that can be
+manually overridden by switching to a different layer if preferred.
+
+Another way the `nextlayer` property can be used is for a non-standard layer
+switching key. So, for example, for the GFF Amharic keyboard phone layout,
+switching back to the base layer uses a `T_ALPHA` key code, in which `nextlayer`
+is set as default. In this case, it is also necessary to add a rule to the
+keyboard program:
+
+```keyman
++ [T_ALPHA] > nul
+```
+
+to ensure that the key's scan code is ignored by the keyboard mapping.
+
+When a key in a touch layout definition includes a **Next Layer** control, this
+takes precedence over setting layer via the
+[`layer`](/developer/language/reference/layer) store (as the **Next Layer**
+control is applied once the rule has finished processing).
+
+### subkey
+
+Arrays of longpress 'subkeys' or pop-up keys can be defined for any key, and
+will appear momentarily after the key is touched if not immediately released.
+This provides a major advantage over physical desktop keyboards in that many
+more keys can be made available from a single layer, without cluttering up the
+basic appearance of the layout. For the GFF Amharic keyboard, we have already
+noted how such subkey arrays are used to manage the extra keys that, on the
+desktop keyboard, would appear in the shift layer. But they are also used to
+provide another way to enter the two different types of each syllable-initial
+vowels (glottal or pharyngeal), as a visual alternative to pressing the key
+twice.
+
+The same properties that are defined for standard keys can also be specified for
+each subkey except that the width of each key in a subkey array will always be
+the same as the width of the key that causes the subkeys to be shown, and key
+spacing always uses the default padding value.
+
+The GFF Amharic keyboard, like many others, is mnemonic, so it is useful to also
+display the standard key cap letter that would appear on the key of a desktop
+keyboard. This is enabled globally in the On-Screen layout editor and applies to
+both the On-Screen keyboard and touch layouts.
+
+## JSON Schema
+
+```json
+{
+ "$schema": "http://json-schema.org/schema#",
+ "$ref": "#/definitions/touch-layout",
+ "$comment": "Version: 16.0",
+ "description": "A Keyman Touch Layout file, per version 16.0, clean spec, no legacy data or types",
+
+ "definitions": {
+ "touch-layout": {
+ "type": "object",
+ "properties": {
+ "tablet": { "$ref": "#/definitions/platform" },
+ "phone": { "$ref": "#/definitions/platform" },
+ "desktop": { "$ref": "#/definitions/platform" }
+ },
+ "minProperties": 1,
+ "additionalProperties": false
+ },
+
+ "platform": {
+ "type": "object",
+ "properties": {
+ "font": { "$ref": "#/definitions/font-spec" },
+ "fontsize": { "$ref": "#/definitions/fontsize-spec" },
+ "layer": { "$ref": "#/definitions/layers" },
+ "displayUnderlying": { "type": "boolean" },
+ "defaultHint": { "type": "string", "enum": ["none","dot","longpress","multitap","flick","flick-n","flick-ne","flick-e","flick-se","flick-s","flick-sw","flick-w","flick-nw"] }
+ },
+ "required": ["layer"],
+ "additionalProperties": false
+ },
+
+ "layers": {
+ "type": "array",
+ "items": { "$ref": "#/definitions/layer" },
+ "minItems": 1
+ },
+
+ "layer": {
+ "type": "object",
+ "properties": {
+ "id": { "$ref": "#/definitions/layer-id" },
+ "row": { "$ref": "#/definitions/rows" }
+ },
+ "required": ["id", "row"],
+ "additionalProperties": false
+ },
+
+ "layer-id": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9_-]+$"
+ },
+
+ "rows": {
+ "type": "array",
+ "items": { "$ref": "#/definitions/row" },
+ "minItems": 1
+ },
+
+ "row": {
+ "type": "object",
+ "properties": {
+ "id": { "$ref": "#/definitions/row-id" },
+ "key": { "$ref": "#/definitions/keys" }
+ },
+ "required": ["id", "key"],
+ "additionalProperties": false
+ },
+
+ "row-id": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 100
+ },
+
+ "keys": {
+ "type": "array",
+ "items": { "$ref": "#/definitions/key" },
+ "minItems": 1
+ },
+
+ "key": {
+ "type": "object",
+ "properties": {
+ "id": { "$ref": "#/definitions/key-id" },
+ "text": { "type": "string" },
+ "layer": { "$ref": "#/definitions/layer-id" },
+ "nextlayer": { "$ref": "#/definitions/layer-id" },
+ "font": { "$ref": "#/definitions/font-spec" },
+ "fontsize": { "$ref": "#/definitions/fontsize-spec" },
+ "sp": { "$ref": "#/definitions/key-sp" },
+ "pad": { "$ref" : "#/definitions/key-pad" },
+ "width": { "$ref" : "#/definitions/key-width" },
+ "sk": { "$ref": "#/definitions/subkeys" },
+ "flick": { "$ref": "#/definitions/flick" },
+ "multitap": { "$ref": "#/definitions/subkeys" },
+ "hint": { "type": "string" }
+ },
+ "anyOf": [
+ {"required": ["id"]},
+ {"required": ["sp"]},
+ {"required": ["sk"]},
+ {"required": ["flick"]},
+ {"required": ["multitap"]}
+ ],
+ "additionalProperties": false
+ },
+
+ "key-id": {
+ "type": "string",
+ "pattern": "^[TKUtku]_[a-zA-Z0-9_]+$"
+ },
+
+ "key-sp": {
+ "type": "integer",
+ "enum": [0, 1, 2, 8, 9, 10]
+ },
+
+ "key-pad": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 100000
+ },
+
+ "key-width": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 100000
+ },
+
+ "subkeys": {
+ "type": "array",
+ "items": { "$ref": "#/definitions/subkey" },
+ "minItems": 1
+ },
+
+ "subkey": {
+ "type": "object",
+ "properties": {
+ "id": { "$ref": "#/definitions/key-id" },
+ "text": { "type": "string" },
+ "layer": { "$ref": "#/definitions/layer-id" },
+ "nextlayer": { "$ref": "#/definitions/layer-id" },
+ "font": { "$ref": "#/definitions/font-spec" },
+ "fontsize": { "$ref": "#/definitions/fontsize-spec" },
+ "sp": { "$ref": "#/definitions/key-sp" },
+ "pad": { "$ref" : "#/definitions/key-pad" },
+ "width": { "$ref" : "#/definitions/key-width" }
+ },
+ "required": ["id"],
+ "additionalProperties": false
+ },
+
+ "flick": {
+ "type": "object",
+ "patternProperties": {
+ "^(n|s|e|w|ne|nw|se|sw)$": { "$ref": "#/definitions/subkey" }
+ },
+ "minProperties": 1,
+ "additionalProperties": false
+ },
+
+ "font-spec": {
+ "type": "string"
+ },
+
+ "fontsize-spec": {
+ "type": "string"
+ }
+ }
+}
+```
diff --git a/developer/docs/help/reference/file-types/kmn.md b/developer/docs/help/reference/file-types/kmn.md
new file mode 100644
index 00000000000..0bf386607ce
--- /dev/null
+++ b/developer/docs/help/reference/file-types/kmn.md
@@ -0,0 +1,21 @@
+---
+title: KMN files
+---
+
+Used by:
+: Keyman Developer .
+
+Description:
+: A .KMN file is a keyboard source file. This holds all the code used
+ by a Keyman Desktop keyboard, in plain text.
+
+Details:
+: Compiles into a Keyman keyboard ([.KMX](kmx)) file.
+
+Distributed with keyboard:
+: This is a keyboard development file and should only be distributed
+ with your package if you want to make the keyboard source code
+ available.
+
+Reference:
+: [Keyman keyboard language](/developer/language/)
diff --git a/developer/docs/help/reference/file-types/kmp.md b/developer/docs/help/reference/file-types/kmp.md
new file mode 100644
index 00000000000..b79bd663be4
--- /dev/null
+++ b/developer/docs/help/reference/file-types/kmp.md
@@ -0,0 +1,37 @@
+---
+title: KMP files
+---
+
+Used by:
+: All Keyman and custom Keyman Engine products can use keyboard
+ packages.
+: Keyman for Android and Keyman for iPhone and iPad can use lexical
+ model packages.
+
+Description:
+: A .KMP file is a Keyman Package file for distributing keyboards or
+ lexical models.
+
+Details:
+: A .KMP file is compiled from a Keyman Package source file
+ ([.KPS](kps)) using the Package Editor in
+ Keyman Developer . Normal contents
+ of a Keyman keyboard Package are one or more keyboards with fonts,
+ documentation, and On Screen Keyboard ([.KVK](kvk)) files. Keyman
+ Developer will also include a [metadata](metadata) file in the
+ package. Lexical model packages contain one lexical model instead of
+ keyboards.
+
+Distributed with keyboard:
+: A Keyman keyboard Package file (.KMP) can include keyboard files
+ ([.KMX](kmx)/.JS), fonts, documentation, and On Screen Keyboard
+ ([.KVK](kvk)) files. Do not include source files ([.KMN](kmn)/.KVKS)
+ in the Package. The Keyman Package is normally distributed instead
+ of the plain keyboard file ([.KMX](kmx)) in order to include the
+ extra files.
+
+Distributed with lexical model:
+: A Keyman lexical model Package file (.KMP) includes one lexical
+ model file ([.MODEL.JS)](model-js), and documentation files. Do not
+ include lexical model source files ([.MODEL.TS](model-ts) or
+ [.TSV](tsv)) in the Package.
diff --git a/developer/docs/help/reference/file-types/kmx.md b/developer/docs/help/reference/file-types/kmx.md
new file mode 100644
index 00000000000..a4351688a52
--- /dev/null
+++ b/developer/docs/help/reference/file-types/kmx.md
@@ -0,0 +1,26 @@
+---
+title: KMX files
+---
+
+Used by:
+: Keyman Desktop ,
+ Keyman for macOS , and
+ Keyman for Linux .
+
+Description:
+: A .KMX file is a compiled keyboard file.
+
+Details:
+: A .KMX file is compiled in Keyman
+ Developer from the Keyman source file ([.KMN](kmn)) and icon
+ for the keyboard (.ICO/.BMP). Note that .KMX files are not used for
+ Keyman for Android and
+ Keyman for iPhone and iPad .
+
+Distributed with keyboard:
+: This is the keyboard file. It must be distributed with your package
+ to allow people to type using the keyboard you have developed.
+
+Reference:
+* [KMX binary file format specification](https://github.com/keymanapp/keyman/blob/master/docs/file-formats/kmx-file-format.md)
+* [KMX+ binary file format specification](https://github.com/keymanapp/keyman/blob/master/docs/file-formats/kmx-plus-file-format.md)
diff --git a/developer/docs/help/reference/file-types/kpj.md b/developer/docs/help/reference/file-types/kpj.md
new file mode 100644
index 00000000000..f990fb98198
--- /dev/null
+++ b/developer/docs/help/reference/file-types/kpj.md
@@ -0,0 +1,17 @@
+---
+title: KPJ files
+---
+
+Used by:
+: Keyman Developer .
+
+Description:
+: A .KPJ file is a Keyman Developer Project file.
+
+Details:
+: .KPJ files are used in Keyman
+ Developer to organise keyboards and other files currently in
+ development.
+
+Distributed with keyboard:
+: No. This is a development file and should not be distributed.
diff --git a/developer/docs/help/reference/file-types/kps.md b/developer/docs/help/reference/file-types/kps.md
new file mode 100644
index 00000000000..ef4095d9aee
--- /dev/null
+++ b/developer/docs/help/reference/file-types/kps.md
@@ -0,0 +1,27 @@
+---
+title: KPS files
+---
+
+Used by:
+: Keyman Developer .
+
+Description:
+: A .KPS file is a Keyman package source file.
+
+Details:
+: A .KPS file is created using the Package Editor in
+ Keyman Developer . It specifies what
+ files are to be included in the package. It is compiled into a
+ Keyman Package file ([.KMP](kmp)).
+
+Distributed with keyboard:
+: No. This is a development file and should not be distributed.
+
+## File format
+
+The optional `` section of the file can be included to
+customise the text in the bootstrap installer. The default strings are
+found in the file:
+[strings.xml](https://github.com/keymanapp/keyman/blob/stable-14.0/windows/src/desktop/setup/locale/en/strings.xml).
+You can add your own strings for a given package which are used when
+compiling as a bundled installer.
diff --git a/developer/docs/help/reference/file-types/kvk.md b/developer/docs/help/reference/file-types/kvk.md
new file mode 100644
index 00000000000..77039aa4c1b
--- /dev/null
+++ b/developer/docs/help/reference/file-types/kvk.md
@@ -0,0 +1,21 @@
+---
+title: KVK files
+---
+
+Used by:
+: Keyman Desktop .
+
+Description:
+: A .KVK file is a Keyman On Screen Keyboard file.
+
+Details:
+: A .KVK file is created in Keyman
+ Developer from the Keyman On Screen Keyboard source file
+ ([.KVKS](kvks)). Web, Android and iOS keyboards, when compiled,
+ embed the .KVK content into the output file, but desktop keyboards
+ (Windows and macOS) require the .KVK file to be included with the
+ keyboard at distribution time.
+
+Distributed with keyboard:
+: A .KVK file is associated with a specific keyboard, and must be
+ distributed with that keyboard to be usable.
diff --git a/developer/docs/help/reference/file-types/kvks.md b/developer/docs/help/reference/file-types/kvks.md
new file mode 100644
index 00000000000..cefd6f6643a
--- /dev/null
+++ b/developer/docs/help/reference/file-types/kvks.md
@@ -0,0 +1,18 @@
+---
+title: KVKS files
+---
+
+Used by:
+: Keyman Developer .
+
+Description:
+: A .KVKS file is a Keyman On Screen Keyboard source file.
+
+Details:
+: A .KVKS file is edited with the Keyboard Editor in
+ Keyman Developer during the
+ keyboard creation process. The file is XML format with UTF-8
+ encoding.
+
+Distributed with keyboard:
+: No. This is a development file and should not be distributed.
diff --git a/developer/docs/help/reference/file-types/metadata.md b/developer/docs/help/reference/file-types/metadata.md
new file mode 100644
index 00000000000..8a3e50deaec
--- /dev/null
+++ b/developer/docs/help/reference/file-types/metadata.md
@@ -0,0 +1,294 @@
+---
+title: Keyman Package Metadata files
+---
+
+Used by:
+: Keyman applications on all platforms except web.
+
+Description:
+: The files `kmp.inf` and `kmp.json` are metadata for a Keyman package file.
+
+Details:
+
+: When Keyman Developer compiles a Keyman package source file ([.kps](kps)), it
+ creates and automatically adds `kmp.inf` and `kmp.json` into the Keyman
+ Package file ([.kmp](kmp)).
+
+ Keyman on all platforms now use `kmp.json` to install the keyboards
+ ([.kmx](kmx)) in the package.
+
+ Older versions of Keyman Desktop for Windows use `kmp.inf`, a parallel format
+ of the package metadata. Note: the lexical model compiler does not add
+ `kmp.inf`, and new functionality is not included. Future versions of Keyman
+ Developer will stop supporting `kmp.inf`.
+
+Package purposes:
+
+: A Keyman Package file can be used to distribute keyboards (most common),
+ lexical models, or user interface localizations for Keyman Desktop.
+
+KMP.JSON
+--------
+
+### The package object
+
+The kmp.json file is a base object described as
+
+`system`
+
+: `Object`
+
+ [`System`](#obj-system) object.
+
+`options`
+
+: `Object`
+
+ An [`Options`](#obj-options) object.
+
+`startMenu`
+
+: `Object`
+
+ [`Start Menu`](#obj-startMenu) object.
+
+`info`
+
+: `Object`
+
+ [`Info`](#obj-info) object.
+
+`files`
+
+: `Object`
+
+ Array of objects, each containing a name and description of each file in in
+ the package
+
+`keyboards`
+
+: `Object`
+
+ Array of [`Keyboard`](#obj-keyboard) objects.
+
+`lexicalModels`
+
+: `Object`
+
+ Array of [`LexicalModel`](#obj-lexicalModel) objects.
+
+### The System object
+
+The `System` object is used by Keyman Desktop to install keyboards
+
+`keymanDeveloperVersion`
+
+: `string`
+
+ The version of Keyman Developer used to create the package file. If undefined,
+ use `'0.0.0.0'`
+
+`fileVersion,`
+
+: `string`
+
+### The Options object
+
+The `Options` object is used by Keyman Desktop to install keyboards
+
+`readmeFile`
+
+: `string`
+
+ A reference to the HTML file in the package for the keyboard package readme,
+ presented before the package is installed.
+
+`graphicFile`
+
+: `string`
+
+ A reference to an image file, .png or .jpeg recommended formats, associated
+ with the keyboard package, and shown at package installation time.
+
+`welcomeFile`
+
+: `string`
+
+ A reference to the HTML file containing keyboard package documentation,
+ available through the help interfaces in Keyman, and shown once after the
+ package is successfully installed. In the past, this file was always called
+ welcome.htm.
+
+`licenseFile`
+
+: `string`
+
+ A HTML file containing the package license.
+
+### The Start Menu object
+
+The `StartMenu` object is used by Keyman Desktop to install windows
+
+`folder`
+
+: `string`
+
+ The folder that Keyman Desktop will create
+
+`items`
+
+: `Array`
+
+ An array of [Item](#obj-item) objects
+
+### The Item Object
+
+The `Item` object
+
+`name`
+
+: `string`
+
+ The item name
+
+`filename`
+
+: `string`
+
+ The filename of the item
+
+`location`
+
+: `string`
+
+ The location for Keyman Desktop to place the item
+
+### The Info object
+
+The `Info` object describes the Keyman package
+
+`name`
+
+: `string`
+
+ The Keyman package name
+
+`version`
+
+: `string`
+
+ The version number of the package in dotted number format. Defaults to `'1.0'` if missing
+
+`copyright`
+
+: `string` optional
+
+ Copyright information
+
+`author`
+
+: `string` optional
+
+ The Keyman package author and email address
+
+`website`
+
+: `string` optional
+
+ Description and URL for additional Keyboard package documentation
+
+### The Keyboard object
+
+The `Keyboard` object describes an individual keyboard in the Keyman package. A package cannot contain both lexical models and keyboards.
+
+`name`
+
+: `string`
+
+ Name of keyboard
+
+`id`
+
+: `string`
+
+ ID of the keyboard, always matches the filename of the keyboard
+
+`rtl`
+
+: `boolean` optional
+
+ `true` if the keyboard targets a right-to-left script. `false` if absent.
+
+`version`
+
+: `string`
+
+ version number of the keyboard in dotted number format. Defaults to `'1.0'` if missing
+
+`languages`
+
+: `Array`
+
+ An array of [`Language`](#obj-language) objects linked to the keyboard.
+
+`displayFont`
+
+: `string` optional
+
+ The filename of the font for input fields (and OSK, if `oskFont` is not present).
+
+`oskFont`
+
+: `string` optional
+
+ The filename of the font for the OSK
+
+### The Language object
+
+The `Language` object describes the language that can be typed with the keyboard
+
+`name`
+
+: `string`
+
+ The name of the language
+
+`id`
+
+: `string`
+
+ [BCP 47 language code](../bcp-47)
+
+### The LexicalModel object
+
+The `LexicalModel` object describes an individual model in the Keyman package. A package cannot contain both lexical models and keyboards.
+
+`name`
+
+: `string`
+
+ Name of model
+
+`id`
+
+: `string`
+
+ ID of the model, always matches the filename of the model
+
+`rtl`
+
+: `boolean` optional
+
+ `true` if the model targets a right-to-left script. `false` if absent.
+
+`version`
+
+: `string`
+
+ version number of the model in dotted number format.
+
+`languages`
+
+: `Array`
+
+ An array of [`Language`](#obj-language) objects linked to the model.
diff --git a/developer/docs/help/reference/file-types/model-js.md b/developer/docs/help/reference/file-types/model-js.md
new file mode 100644
index 00000000000..e8f3cbb2c4b
--- /dev/null
+++ b/developer/docs/help/reference/file-types/model-js.md
@@ -0,0 +1,23 @@
+---
+title: MODEL.JS files
+---
+
+Used by:
+: Keyman for Android and
+ Keyman for iPhone and iPad .
+
+Description:
+: A .MODEL.JS file is a compiled lexical model file.
+
+Details:
+: A .MODEL.JS file is compiled in Keyman
+ Developer from the Keyman lexical model source
+ ([.MODEL.TS](model-ts)) file and ([.TSV](tsv)) wordlist. Note that
+ .MODEL.JS files are only currently supported in
+ Keyman for Android and
+ Keyman for iPhone and iPad .
+
+Distributed with lexical model:
+: This is the lexical model file. It must be distributed as a part of
+ your package to allow people to use the lexical model you have
+ developed.
diff --git a/developer/docs/help/reference/file-types/model-ts.md b/developer/docs/help/reference/file-types/model-ts.md
new file mode 100644
index 00000000000..98447d0283e
--- /dev/null
+++ b/developer/docs/help/reference/file-types/model-ts.md
@@ -0,0 +1,210 @@
+---
+title: MODEL.TS files
+---
+
+Used by:
+: Keyman Developer .
+
+Description:
+: A .MODEL.TS file is a [lexical model](../../guides/lexical-models)
+ definition source file. This holds all the code used by a Keyman
+ lexical model, in plain text.
+
+Details:
+: A .MODEL.TS file is written in the
+ [TypeScript](https://www.typescriptlang.org/) language.
+ Keyman Developer compiles this
+ Keyman lexical model source file which can also reference a
+ ([.TSV](tsv)) wordlist to make a lexical model
+ ([.MODEL.JS](model-js)) file.
+
+## Reference
+
+This is a small [TypeScript](https://www.typescriptlang.org/) source
+code file that tells us where to find the word list file, as well as
+gives us the option to tell the compiler a little bit more about our
+language’s spelling system or *orthography*.
+
+## The model definition template
+
+**Keyman Developer** will provide you with a model definition similar to
+the following.
+
+``` typescript
+/*
+ sencoten 1.0 generated from template.
+
+ This is a minimal lexical model source that uses a tab delimited wordlist.
+ See documentation online at https://help.keyman.com/developer/ for
+ additional parameters.
+*/
+
+
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ wordBreaker: {
+ use: 'default',
+ },
+ sources: ['wordlist.tsv'],
+};
+export default source;
+```
+
+Let's step through this file, line-by-line.
+
+On the first line, we're declaring the source code of a new lexical
+model.
+
+``` typescript
+const source: LexicalModelSource = {
+```
+
+On the second line, we're saying the lexical model will use the
+`trie-1.0` format. The `trie` format creates a lexical model from one or
+more word lists; the `trie` structures the lexical model such that it
+can predict through thousands of words very quickly.
+
+``` typescript
+ format: 'trie-1.0',
+```
+
+On lines 3–5, we're specifying the word breaking algorithm that we want
+to use. Keyman supplies a default algorithm that conforms to the rules
+expected for many Latin-script languages.
+
+``` typescript
+ wordBreaker: {
+ use: 'default',
+ },
+```
+
+On the sixth line, we're telling the `trie` where to find our wordlist.
+
+``` typescript
+ sources: ['wordlist.tsv'],
+```
+
+The seventh line marks the termination of the lexical model source code.
+If we specify any customizations, they **must** be declared above this
+line:
+
+``` typescript
+};
+```
+
+The eighth line is necessary to allow external applications to read the
+lexical model source code.
+
+``` typescript
+export default source;
+```
+
+## Customizing our lexical model
+
+The template, as described in the previous section, is a good starting
+point, and may be all you need for you language. However, most language
+require a few customizations. The `trie` model supports the following
+customizations:
+
+word breaking
+: How to determine when words start and end in the writing system.
+
+search term to key
+: How and when to ignore accents and lettercase
+
+### Word breaking
+
+The `trie` family of lexical models needs to know what a word is in
+running text. In language using the Latin script—like, English, French,
+and SENĆOŦEN—finding words is easy. Words are separated by spaces or
+punctuation. The actual rules for where to find words can get quite
+tricky to describe, but Keyman implements the [Unicode Standard Annex
+\#29 §4.1 Default Word Boundary
+Specification](https://unicode.org/reports/tr29/#Word_Boundaries) which
+works well for most languages. If the default doesn't *quite* work for
+your language, you can [tweak
+it](../../guides/lexical-models/advanced/word-breaker#join).
+
+However, in languages written in other scripts—especially East Asian
+scripts like Chinese, Japanese, Khmer, Lao, and Thai—there are no
+obvious break in between words. For these languages, there must be
+special rules for determining when words start and stop. This is what a
+word breaking function is responsible for. It
+is a little bit of code that looks at some text to determine where the
+words are.
+
+### Search term to key
+
+To look up words quickly, the `trie` model creates a
+search key that takes the latest word (as determined by the
+[word breaking](#toc-word-breaking) and converts it into a “regular”
+form. The purpose of this “regular” form is to make searching for a word
+work, regardless of things such as **accents**, **diacritics**,
+**lettercase**, and minor **spelling variations**. The ”regular” form is
+called the key . Typically, the key is always in
+lowercase, and lacks all accents and diacritics. For example, the key of
+“naïve" is "naive" and the key of Canada is “canada”.
+
+The form of the word that is stored is “regularized” through the use of
+a key function , which you can define in
+TypeScript code.
+
+The key function takes a string, the raw search term, and returns a
+string, being the “regular” key. As an example, consider the **default
+key function**; that is, the key function that is used if you do not
+specify one:
+
+```
+searchTermToKey: function (term) {
+ // Use this pattern to remove common diacritical marks (accents).
+ // See: https://www.compart.com/en/unicode/block/U+0300
+ const COMBINING_DIACRITICAL_MARKS = /[\u0300-\u036f]/g;
+
+ // Lowercase each letter in the string INDIVIDUALLY.
+ // Why individually? Some languages have context-sensitive lowercasing
+ // rules (e.g., Greek), which we would like to avoid.
+ // So we convert the string into an array of code points (Array.from(term)),
+ // convert each individual code point to lowercase (.map(c => c.toLowerCase())),
+ // and join the pieces back together again (.join(''))
+ let lowercasedTerm = Array.from(term).map(c => c.toLowerCase()).join('');
+
+ // Once it's lowercased, we convert it to NFKD normalization form
+ // This does many things, such as:
+ //
+ // - separating characters from their accents/diacritics
+ // e.g., "ï" -> "i" + "¨" (U+0308)
+ // - converting lookalike characters to a canonical ("regular") form
+ // e.g., ";" -> ";" (yes, those are two completely different characters -- U+037E and U+003B!)
+ // - converting "compatible" characters to their canonical ("regular") form
+ // e.g., "𝔥𝔢𝔩𝔩𝔬" -> "hello"
+ let normalizedTerm = lowercasedTerm.normalize('NFKD');
+
+ // Now, using the pattern defined above, replace each accent and diacritic with the
+ // empty string. This effectively removes all accents and diacritics!
+ //
+ // e.g., "i" + "¨" (U+0308) -> "i"
+ let termWithoutDiacritics = normalizedTerm.replace(COMBINING_DIACRITICAL_MARKS, '');
+
+ // The resultant key is lowercased, and has no accents or diacritics.
+ return termWithoutDiacritics;
+},
+```
+
+This should be sufficient for most Latin-based writing systems. However,
+there are cases, such as with SENĆOŦEN, where some characters do not
+decompose into a base letter and a diacritic. In this case, it is
+necessary to write your own key function.
+
+## Version History
+
+Keyman 12
+: **Added**: the `.model.ts` file type.
+
+Keyman 13
+: *No changes.*
+
+Keyman 14
+: **Added**: an alternative syntax for specifying word breakers:
+ `wordBreaker: { 'use': ... }`.
+: **Added**: specify which characters should be used to join with word
+ breakers: `wordBreaker: { 'joinWordsAt': ... }`.
diff --git a/developer/docs/help/reference/file-types/tsv.md b/developer/docs/help/reference/file-types/tsv.md
new file mode 100644
index 00000000000..b926ed95725
--- /dev/null
+++ b/developer/docs/help/reference/file-types/tsv.md
@@ -0,0 +1,60 @@
+---
+title: TSV files
+---
+
+Used by:
+: Keyman Developer and
+ Lexical Model compiler .
+
+Description:
+: A .tsv file or a tab-separated values file
+ contains a word list. This word list is used to predict and correct
+ words using the predictive text functionality.
+
+Details:
+: A .tsv file is a plain-text file containing of tabular data.
+ Spreadsheet programs such as Microsoft Excel and Google Sheets can
+ export into TSV format. TSVs can also be programmatically generated
+ from other data sources. For advanced users, see [File
+ Format](#file-format) for more details.
+
+Distributed with lexical model:
+: No. This is a development file and should not be distributed.
+
+## File format
+
+**For advanced users only**: this documentation is intended for users
+who wish to develop their own word list exporters. Most users can use
+existing word list exporters.
+
+The **lexical model compiler** expects word lists to abide by the
+following **tab-separated values** (TSV) format:
+
+- the file is a **UTF-8** encoded text file
+- newlines are either **LF** or **CRLF**
+- the file **MAY** start with the UTF-8 **byte-order mark** (BOM);
+ that is, if the first three bytes of the file are `EF BB BF`, these
+ will be interpreted as the BOM and will be ignored.
+- the file either consists of a **comment** or an **entry**
+- **comment** lines MUST start with the `#` character on the very
+ first column
+- **entries** are one to three columns, separated by the (horizontal)
+ **tab character** (U+0009)
+- column 1 (**REQUIRED**): the **word form**: can have any character
+ except tab, CR, or LF. Surrounding whitespace characters are
+ trimmed. Quote characters (`'` or `"`) are **NOT** required to
+ surround the text and are **NOT** parsed in any special manner.
+- column 2 (*optional*): the **count**: a non-negative integer
+ specifying how many times this entry has appeared in the corpus.
+ Blank means ‘indeterminate’, and is treated as if the word exists in
+ the corpus, but will be predicted at the lowest possible priority.
+- column 3 (*optional*): **comment**: an informative comment, ignored
+ by this tool.
+
+Source:
+[build-trie.js@029fb7c8](https://github.com/keymanapp/keyman/blob/029fb7c822c5a5619eaca845ecd2e5a2497d3056/developer/js/lexical-model-compiler/build-trie.ts#L21-L40)
+
+## Additional notes
+
+Exporting a spreadsheet from Google Sheets as a TSV will produce
+properly formatted output.
diff --git a/developer/docs/help/reference/file-types/ttf.md b/developer/docs/help/reference/file-types/ttf.md
new file mode 100644
index 00000000000..870a5201788
--- /dev/null
+++ b/developer/docs/help/reference/file-types/ttf.md
@@ -0,0 +1,22 @@
+---
+title: TTF files
+---
+
+Used by:
+: Keyman Developer .
+
+Description:
+: A .ttf file is a TrueType font file. This file is distributed with a
+ keyboard package to be used as a keyboard font or display font. This
+ file is optional; many languages and keyboards do not need to
+ include a font because there will be a font supplied by the
+ operating system.
+
+Details:
+: A .ttf file is a binary file. It is included using the Package
+ Editor in Keyman Developer. Keyman Developer does not create font
+ files.
+
+Distributed with keyboard:
+: This is a font file. It should be distributed with your keyboard
+ package to allow people to see the keyboard output characters.
diff --git a/developer/docs/help/reference/file-types/xml.md b/developer/docs/help/reference/file-types/xml.md
new file mode 100644
index 00000000000..78d24749528
--- /dev/null
+++ b/developer/docs/help/reference/file-types/xml.md
@@ -0,0 +1,18 @@
+---
+title: XML files
+---
+
+Used by:
+: Keyman Developer .
+
+Description:
+: A .XML file can be many different things. For Keyman Developer, XML files
+ are typically LDML keyboards which are authored in XML. This file holds all
+ the code used by a LDML format keyboard, in XML format.
+
+Details:
+: Compiles into a Keyman keyboard ([.KMX](kmx)) file.
+
+Distributed with keyboard:
+: This is a keyboard development file and should not usually be distributed
+ with your package.
diff --git a/developer/docs/help/reference/index.md b/developer/docs/help/reference/index.md
new file mode 100644
index 00000000000..04ba82a125e
--- /dev/null
+++ b/developer/docs/help/reference/index.md
@@ -0,0 +1,13 @@
+---
+title: Keyman Developer Reference
+---
+
+* [Keyman keyboard language guide and reference](/developer/language)
+* [Command line compiler - kmc](kmc)
+* [Compiler messages](messages)
+* [Keyman keyboard source file layout](file-layout)
+* [File types](file-types)
+* [BCP 47 language codes](bcp-47)
+* [Tools](tools)
+* [Custom editor themes](editor-themes)
+* [User settings](user-settings)
\ No newline at end of file
diff --git a/developer/docs/help/reference/kmc/api/index.md b/developer/docs/help/reference/kmc/api/index.md
new file mode 100644
index 00000000000..e39a04f8aa6
--- /dev/null
+++ b/developer/docs/help/reference/kmc/api/index.md
@@ -0,0 +1,43 @@
+---
+title: kmc API Reference
+---
+
+kmc is implemented in Typescript as a set of compiler modules. Each module has a
+public API, documented in README.md in the corresponding repository folder.
+
+This section is intended for software developers who want to integrate kmc into
+their own toolchains. For documentation on using `kmc`, see [kmc command line
+compiler](../cli).
+
+# Modules
+
+Module Name | NPM Package | GitHub Source | Description
+-------------------------------------------------|-------------------------------------------------------|------------------------------------|---------------
+[kmc](../cli) | [@keymanapp/kmc][kmc-npm] | [GitHub][kmc-github] | The official command-line interface for all of the various compilers in Keyman Developer.
+[kmc-analyze](../../api/kmc-analyze) | [@keymanapp/kmc-analyze][kmc-analyze-npm] | [GitHub][kmc-analyze-github] | Provides Keyman keyboard analysis tools.
+[kmc-keyboard-info](../../api/kmc-keyboard-info) | [@keymanapp/kmc-keyboard-info][kmc-keyboard-info-npm] | [GitHub][kmc-keyboard-info-github] | Builds a .keyboard_info file from a Keyman keyboard project.
+[kmc-kmn](../../api/kmc-kmn) | [@keymanapp/kmc-kmn][kmc-kmn-npm] | [GitHub][kmc-kmn-github] | Builds .kmn keyboards into .kmx binary keyboard files.
+[kmc-ldml](../../api/kmc-ldml) | [@keymanapp/kmc-ldml][kmc-ldml-npm] | [GitHub][kmc-ldml-github] | Builds LDML .xml keyboards into Keyman .kmx binary keyboard files.
+[kmc-model](../../api/kmc-model) | [@keymanapp/kmc-model][kmc-model-npm] | [GitHub][kmc-model-github] | Builds .model.ts lexical models into .model.js files.
+[kmc-model-info](../../api/kmc-model-info) | [@keymanapp/kmc-model-info][kmc-model-info-npm] | [GitHub][kmc-model-info-github] | Builds a .model_info file from a Keyman lexical model project.
+[kmc-package](../../api/kmc-package) | [@keymanapp/kmc-package][kmc-package-npm] | [GitHub][kmc-package-github] | Builds .kps Keyman package source files into binary .kmp Keyman package files.
+
+[kmc-npm]: https://npmjs.com/package/@keymanapp/kmc
+[kmc-analyze-npm]: https://npmjs.com/package/@keymanapp/kmc-analyze
+[kmc-keyboard-info-npm]: https://npmjs.com/package/@keymanapp/kmc-keyboard-info
+[kmc-kmn-npm]: https://npmjs.com/package/@keymanapp/kmc-kmn
+[kmc-ldml-npm]: https://npmjs.com/package/@keymanapp/kmc-ldml
+[kmc-model-npm]: https://npmjs.com/package/@keymanapp/kmc-model
+[kmc-model-info-npm]: https://npmjs.com/package/@keymanapp/kmc-model-info
+[kmc-package-npm]: https://npmjs.com/package/@keymanapp/kmc-package
+[common-types-npm]: https://npmjs.com/package/@keymanapp/common-types
+
+[kmc-github]: https://github.com/keymanapp/keyman/tree/master/developer/src/kmc
+[kmc-analyze-github]: https://github.com/keymanapp/keyman/tree/master/developer/src/kmc-analyze
+[kmc-keyboard-info-github]: https://github.com/keymanapp/keyman/tree/master/developer/src/kmc-keyboard-info
+[kmc-kmn-github]: https://github.com/keymanapp/keyman/tree/master/developer/src/kmc-kmn
+[kmc-ldml-github]: https://github.com/keymanapp/keyman/tree/master/developer/src/kmc-ldml
+[kmc-model-github]: https://github.com/keymanapp/keyman/tree/master/developer/src/kmc-model
+[kmc-model-info-github]: https://github.com/keymanapp/keyman/tree/master/developer/src/kmc-model-info
+[kmc-package-github]: https://github.com/keymanapp/keyman/tree/master/developer/src/kmc-package
+[common-types-github]: https://github.com/keymanapp/keyman/tree/master/common/web/types
diff --git a/developer/docs/help/reference/kmc/cli/get-started.md b/developer/docs/help/reference/kmc/cli/get-started.md
new file mode 100644
index 00000000000..9147eebda25
--- /dev/null
+++ b/developer/docs/help/reference/kmc/cli/get-started.md
@@ -0,0 +1,164 @@
+---
+title: Get started with kmc
+---
+
+This package provides a command-line interface to the Keyman Developer compiler
+toolchain, `kmc`.
+
+## Install kmc
+
+`kmc` is available as:
+* a part of Keyman Developer (Windows)
+* an npm package, and
+* a zip download
+
+Hint: Unlike previous versions of Keyman Developer, version 17 of kmc does not
+require WINE to run the command line tools on Linux or macOS.
+
+### Keyman Developer integration (Windows only)
+
+kmc is included with a default installation of Keyman Developer, including a
+runtime of node.js, and will be on the system path by default. No additional
+configuration or installation is required.
+
+### npm (Windows, macOS, and Linux)
+
+kmc is also available as an npm package,
+[@keymanapp/kmc](https://npmjs.com/package/@keymanapp/kmc).
+
+You'll need [node.js](https://nodejs.org/), version 18.0 or later.
+
+```shell
+npm install -g @keymanapp/kmc
+```
+
+### Zip download (Windows, macOS, and Linux)
+
+kmc is also available as a zip download from
+[keyman.com/developer/download](https://keyman.com/developer/download),
+or can be installed from the command line (`curl` and `unzip` required):
+
+```shell
+# To build keyboards and packages:
+mkdir kmc
+cd kmc
+# hint: the download is currently called 'kmcomp', although the
+# compiler is now called 'kmc'.
+curl -L https://keyman.com/go/download/kmcomp -o kmc.zip
+unzip kmc.zip
+# Optionally, add kmc to your PATH
+```
+
+## The five minute quick start
+
+### 1. Download a sample keyboard project
+
+
+
+We'll download a sample project from GitHub for Khmer. If you do not have the
+command-line git tools installed, you can visit the [repository
+website](https://github.com/keyman-keyboards/khmer_angkor) and download it as a
+zip file instead.
+
+```shell
+git clone https://github.com/keyman-keyboards/khmer_angkor
+```
+
+This will have created a new folder called `khmer_angkor/`.
+
+### 2. Build the project
+
+Now, we'll build our keyboard project with kmc.
+
+```shell
+cd khmer_angkor
+kmc build .
+```
+
+And... that's it! We'll now have a compiled keyboard and package in the `build/`
+subfolder. The file `build/khmer_angkor.kmp` can be installed into any of the
+Keyman apps, and `build/khmer_angkor.js` can be added to KeymanWeb.
+
+### 3. Install the keyboard
+
+**Windows**: we can install this keyboard using [`kmshell`][windows-install-keyboard]:
+
+```cmd
+"%ProgramFiles(x86)%\Keyman\Keyman Desktop\kmshell" -i build\khmer_angkor.kmp -s
+```
+
+Alternatively you can double-click the .kmp package file in Windows Explorer to
+install it.
+
+**Linux**: we'd use the following
+[`km-package-install`][linux-install-keyboard]
+command:
+
+```shell
+km-package-install -f build/khmer_angkor.kmp
+```
+
+**macOS**: open Keyman Configuration and drop the package khmer_angkor.kmp file
+onto the Keyman Configuration window.
+
+**Android**: send khmer_angkor.kmp to your Android device, and install it from the
+hamburger menu in the Keyman app.
+
+**iOS**: send khmer_angkor.kmp to your iOS device, and install it from the
+hamburger menu in the Keyman app.
+
+**Web**: copy khmer_angkor.js to your website, then [load it with KeymanWeb][load-keymanweb-keyboard]:
+
+```js
+keyman.addKeyboards({
+ id:'khmer_angkor', // The keyboard's unique identification code.
+ name:'Khmer Angkor', // The keyboard's user-readable name.
+ language:{
+ id:'km', // A BCP 47 code uniquely identifying the language.
+ name:'Khmer' // The language's name.
+ },
+ filename:'./khmer-angkor.js',
+});
+```
+
+## File layout
+
+See [file layout][file-layout] for details on the standard source file layout
+that Keyman Developer works best with.
+
+## Reference and Examples
+
+### kmc - command line compiler
+
+[kmc][kmc] is the command line compiler. You can use it to compile
+all Keyman files.
+
+The most common command will be `kmc build`:
+
+`kmc build project.kpj`
+: Compile all components of a keyboard or model project named `project.kpj`
+KMComp will respect the path settings within the project file. This is the
+recommended way to build, as it will build keyboards, models and packages all in
+one step. You can also call `kmc build ` to build the project in the
+referenced folder, e.g. `kmc build .`.
+
+* [kmc reference][kmc]
+
+### KMConvert
+
+[KMConvert](../../../context/kmconvert) generates keyboards and models from templates,
+and converts keyboard layouts between different formats.
+
+The [New Project dialog](../../../context/new-project) in Keyman Developer provides a
+graphical version of the most common functionality in KMConvert.
+
+* [KMConvert reference](../../../context/kmconvert)
+
+**Note:** KMConvert is currently a Windows executable, and will be integrated
+into kmc in an upcoming version of Keyman.
+
+[kmc]: ./reference
+[file-layout]: ../../file-layout
+[load-keymanweb-keyboard]: /developer/engine/web/current-version/guide/adding-keyboards
+[linux-install-keyboard]: /products/linux/current-version/reference/km-package-install
+[windows-install-keyboard]: /knowledge-base/98
diff --git a/developer/docs/help/reference/kmc/cli/index.md b/developer/docs/help/reference/kmc/cli/index.md
new file mode 100644
index 00000000000..808e053a2f8
--- /dev/null
+++ b/developer/docs/help/reference/kmc/cli/index.md
@@ -0,0 +1,7 @@
+---
+title: Command line compiler - kmc
+---
+
+* [Get started with kmc](get-started)
+* [kmc command line reference](reference)
+* [Migrating from kmcomp to kmc](kmcomp-migration)
diff --git a/developer/docs/help/reference/kmc/cli/kmcomp-migration.md b/developer/docs/help/reference/kmc/cli/kmcomp-migration.md
new file mode 100644
index 00000000000..c864e1bc901
--- /dev/null
+++ b/developer/docs/help/reference/kmc/cli/kmcomp-migration.md
@@ -0,0 +1,125 @@
+---
+title: Migrating from kmcomp to kmc
+---
+
+`kmcomp` was the command-line compiler for Keyman Developer through version
+16.0. Version 17.0 replaces `kmcomp` with `kmc`.
+
+The lexical model command-line tooling, `kmlmc`, `kmlmp`, and `kmlmi`, are all
+still present in version 17, but are deprecated, as the same tasks can be
+performed with `kmc`.
+
+## Benefits
+
+* Unlike `kmcomp`, which was a Windows-only console application, `kmc` is a
+ cross-platform application, running on Windows, Linux, and macOS, built in
+ node.js.
+* `kmc` parameters have been redesigned to be more consistent, and easily
+ extensible for new functionality.
+* `kmc` can run in a batch mode that is up to 100x faster than `kmcomp`.
+* `kmc` can be easily integrated into GitHub Actions and other continuous
+ integration tooling.
+
+## Compiling a project
+
+To compile a project, `kmcomp` used a command line such as:
+
+```bash
+kmcomp path\to\my_keyboard.kpj
+```
+
+With `kmc`, you would use:
+
+```bash
+kmc build path/to/my_keyboard.kpj
+# or you can specify just the folder:
+kmc build path/to/
+# If you are in the same folder as the .kpj:
+kmc build .
+```
+
+This is the recommended way of using `kmc` -- always build an entire project,
+rather than just the keyboard.
+
+## Compiling a keyboard
+
+To compile a keyboard, `kmcomp` builds used command lines such as:
+
+```bash
+# building a .kmx
+kmcomp my_keyboard.kmn my_keyboard.kmx
+# building a .js (for KeymanWeb)
+kmcomp my_keyboard.kmn my_keyboard.js
+```
+
+With `kmc`, you would use:
+
+```bash
+# building all outputs
+kmc build my_keyboard.kmn
+# or to specify output files (.js inferred from .kmx, and always built)
+kmc build my_keyboard.kmn -o my_keyboard.kmx
+```
+
+## Compiling a package
+
+To compile a package, `kmcomp` builds used command lines such as:
+
+```bash
+kmcomp my_keyboard.kps
+```
+
+With `kmc`, you would use:
+
+```bash
+kmc build my_keyboard.kps
+# to specify output file or path
+kmc build my_keyboard.kps -o path/to/my_keyboard.kmp
+```
+
+## Map of parameters
+
+The following table lists common parameters in `kmcomp`, and the corresponding
+parameter in `kmc`:
+
+kmcomp | kmc | notes
+-----------------------|---------------------------------------|-------------
+`-h`, `-help` | `-h`, `--help` | Note that `kmc --help` can be used for further detail on subcommands, e.g. `kmc build --help`
+`-s` | `-l warn`, `--log-level warn` | Silent option, suppresses informational and hint-level messages
+`-ss` | `-l silent`, `--log-level silent` | Super-silent option, suppresses all messages, except fatal internal errors
+`-nologo` | not required | `kmc` does not emit a compiler description
+`-c` | N/A | `kmc` does not currently support the `clean` command, this will be supported in future versions
+`-d` | `-d`, `--debug` | Include debug information in output files
+`-w` | `-w`, `--compiler-warnings-as-errors` | Causes warnings to fail the build; overrides project-level option
+`-cfc` | not required | Filename convention checks are now standard hints/warnings in the build
+`-t` | N/A | Build specified target from .kpj, not supported in kmc
+`-color` | `--color` | Force colorization for log messages
+`-no-color` | `--no-color` | No colorization for log messages
+`-no-compiler-version` | `--no-compiler-version` | Exclude compiler version metadata from output files
+
+## Compiling a lexical model
+
+`kmlmc` and `kmlmp` were separate tools in earlier versions of Keyman Developer,
+for compiling lexical models and lexical model packages. They have both been
+replaced with `kmc`.
+
+Old method, using kmlmc and kmlmp:
+
+```bash
+kmlmc file.model.ts
+# or specifying output filename
+kmlmc -o output/path/file.model.js file.model.ts
+kmlmp file.model.kps
+```
+
+New method, using kmc:
+
+```bash
+# recommended, build the model project:
+kmc build .
+# building single files:
+kmc build file.model.ts
+kmc build file.model.ts -o output/path/file.model.js
+kmc build file.model.kps
+```
+
diff --git a/developer/docs/help/reference/kmc/cli/messages/index.md b/developer/docs/help/reference/kmc/cli/messages/index.md
new file mode 100644
index 00000000000..1655e0f4888
--- /dev/null
+++ b/developer/docs/help/reference/kmc/cli/messages/index.md
@@ -0,0 +1,6 @@
+---
+title: kmc Compiler Messages
+---
+
+This section lists messages that the kmc compiler may generate in use, describes
+circumstances that cause the messages to arise, and how to resolve them.
diff --git a/developer/docs/help/reference/kmc/cli/reference.md b/developer/docs/help/reference/kmc/cli/reference.md
new file mode 100644
index 00000000000..bff696d0d0d
--- /dev/null
+++ b/developer/docs/help/reference/kmc/cli/reference.md
@@ -0,0 +1,324 @@
+---
+title: kmc command line reference
+---
+
+kmc is the command line compiler toolset for Keyman 17.0 and later versions.
+In Keyman Developer, it is located in `%ProgramFiles(x86)%\Keyman\Keyman Developer`.
+kmc is also available as an npm module for Windows, macOS and Linux developers:
+
+```
+npm install -g @keymanapp/kmc
+```
+
+kmc does more than just compile keyboards. It builds packages, lexical models,
+projects, keyboards, Windows installers, and more. It provides analysis tools
+for keyboard data.
+
+kmc will be extended to generate new keyboard and lexical model projects,
+support renaming or cloning of existing projects, importing keyboard data from
+other formats, and running keyboard unit tests.
+
+kmc [replaces kmcomp](kmcomp-migration) from earlier versions of Keyman Developer.
+
+The following parameters are available:
+
+## `kmc` commands
+
+`kmc build [infile...]`, `kmc build file [infile...]`
+
+: Compile one or more Keyman files. Takes Keyman keyboard source files, and
+ compiles them into the binary formats used by the Keyman apps. Supports
+ building:
+ * Keyman projects (.kpj or folder)
+ * Keyman keyboards (.kmn)
+ * LDML keyboards (.xml)
+ * Keyboard packages - bundles of keyboard files, fonts, documentation for
+ distribution (.kps)
+ * Lexical models (.model.ts)
+
+ The `file` subcommand is the default command for `kmc build`, so can be
+ omitted.
+
+ File lists can be referenced with @filelist.txt. If no input file is supplied,
+ kmc will attempt to build a project file in the current folder.
+
+`kmc build ldml-test-data`
+
+: Converts LDML keyboard test .xml file to .json.
+
+`kmc build windows-package-installer`
+
+: Builds a .exe installer for a keyboard, together with the Keyman installer,
+ for Windows only.
+
+`kmc analyze osk-char-use [infile...]`
+
+: Analyze on screen keyboard files for character usage
+
+`kmc analyze osk-rewrite-from-char-use -m mapping-file [infile...]`
+
+: Rewrites On Screen Keyboard files from source mapping
+
+`kmc message [message...]`
+
+: Describes one or more compiler messages in greater detail
+
+## `kmc` global options
+
+`-h`, `--help`
+
+: Display help on kmc; note that `kmc --help` can be used for further detail on
+ subcommands, e.g. `kmc build --help`
+
+`-V`, `--version`
+
+: Prints the version number of kmc
+
+`--no-error-reporting`, `--error-reporting`
+
+: Enable or disable error reporting to keyman.com, overriding [user
+ settings](../../user-settings). Error reporting is for fatal errors in the
+ compiler, and not errors in compiled files. No user data is sent in error
+ reports, although some filenames and paths may be present in the diagnostic
+ data attached to the report.
+
+`-l `, `--log-level `
+
+: Controls the level of logging to console for messages relating to the
+ compilation process. The options are:
+ * `silent`: suppresses all messages (except fatal internal errors)
+ * `error`: only emits compilation errors
+ * `warn`: only emits compilation errors and warnings
+ * `hint`: emits compilation errors, warnings, and hints
+ * `info` (default): emits compilation errors, warnings, hints, and
+ informational messages
+ * `debug`: emits all compilation messages, plus internal debug messages
+
+ Logging of specific [messages](messages) can be controlled with `--message`, `-m`.
+
+ Note that when warnings are treated as errors
+ (`--compiler-warnings-as-errors`, `-w`), they will still be logged as
+ warnings, so suppressing warnings while using this flag may be confusing.
+
+## `kmc build` options
+
+`--color`, `--no-color`
+
+: Controls colorization for log messages, using ANSI color controls. If both of
+ these settings are omitted, kmc will attempt to detect from console, and will
+ use colorization for interactive terminals, and no colorization when
+ redirection is being used.
+
+`-d`, `--debug`
+
+: Include debug information in output files. Debug information is used for
+ interactive debugging of .kmx files within the Keyman Developer IDE. This
+ flag also produces pretty printed .js files for web keyboards, making
+ interactive debugging of web keyboards simpler.
+
+`-w`, `--compiler-warnings-as-errors` vs `-W`, `--no-compiler-warnings-as-errors`
+
+: Controls whether or not warnings fail the build; overrides project-level
+ warnings-as-errors option. Most compiler warnings are an indication that
+ something is not right in the source code, even though the compiler can
+ produce a result. This strict compilation mode helps to ensure that problems
+ are caught early, and is recommended.
+
+`-m `, `--message `
+
+: Adjusts the severity of info, hint or warning messages. Error and fatal error
+ messages can not be adjusted. Message severity can be adjusted to:
+ * `disable` (default): suppresses the message altogether
+ * `info`: converts the message to an informational severity
+ * `hint`: converts the message to a hint severity
+ * `warn`: converts the message to a warning severity
+ * `error`: raises the message to an error severity
+
+ This may be used to suppress a warning message that would otherwise fail the
+ build, if used in conjunction with `-w`, `--compiler-warnings-as-errors`
+
+ This option may be repeated to adjust multiple messages. The `-m` option must
+ be specified each time.
+
+`--no-compiler-version`
+
+: Excludes compiler version metadata from output. This is helpful for producing
+ files that will be identical regardless of the compiler version, for
+ regression testing.
+
+`--no-warn-deprecated-code`
+
+: Turns off warnings (CWARN_HeaderStatementIsDeprecated,
+ CWARN_LanguageHeadersDeprecatedInKeyman10) for deprecated code styles
+
+`--log-format `
+
+: Output log format. The available options are:
+ * `formatted` (default): emits log messages in a human-readable format
+ * `tsv`: emits log messages in UTF-8 tab-separated format. This format will be
+ stable across versions of kmc. The format has the following fields:
+ * filename
+ * line number
+ * severity
+ * code
+ * message
+
+`-o `, `--out-file `
+
+: Overrides the default path and filename for the output file(s). Note that
+ some compilers emit multiple files, in which case, the output filenames
+ will vary by file extension.
+
+## `kmc build file` additional options
+
+`--for-publishing`
+
+: Verifies that project meets @keymanapp repository requirements. This also
+ causes a .keyboard-info or .model-info file to be emitted when compiling the
+ project (which can also be controlled at a project level with the
+ `skipMetadataFiles` option). This option is only valid for compiling projects.
+
+### Examples
+
+```shell
+kmc build project.kpj
+```
+
+Compile all components of a keyboard or model project named `project.kpj`.
+kmc will respect the path settings within the project file. This is the
+recommended way to build, as it will build keyboards, models and packages all in
+one step. You can also call `kmc build ` to build the project in the
+referenced folder, e.g. `kmc build .`.
+
+```shell
+kmc build keyboard.kmn
+```
+
+Compile a keyboard file to a .kmx (desktop targets) and/or .js (web/touch
+targets). If an output file is not specified, writes to the same folder as the
+keyboard.
+
+```shell
+kmc build package.kps
+```
+
+Compile a package file to a .kmp (all targets). All included keyboards must
+already be compiled.
+
+
+## `kmc build windows-package-installer` additional options
+
+`--msi `
+
+: Full path of keymandesktop.msi to bundle into the installer. This file can be
+ downloaded from https://downloads.keyman.com/windows/stable (/version).
+
+`--exe `
+
+: Location of setup.exe. This file can be downloaded from
+ https://downloads.keyman.com/windows/stable (/version).
+
+`--license `
+
+: Location of license.txt for Keyman for Windows.
+
+`--title-image [titleImageFilename]`
+
+: Location of title image file. This should be a .png, .jpg, or .bmp file which
+ replaces the standard 'Keyman for Windows' image in the bootstrap installer.
+
+`--app-name [applicationName]`
+
+: Installer property: name of the application to be installed (default: "Keyman")
+
+`--start-disabled`
+
+: Installer property: do not enable keyboards after installation completes
+
+`--start-with-configuration`
+
+: Installer property: start Keyman Configuration after installation completes
+
+### Examples
+
+#### Windows, command prompt (all one line)
+
+```bat
+kmc build windows-package-installer .\khmer_angkor.kps
+ --msi "C:\Program Files (x86)\Common Files\Keyman\Cached Installer Files\keymandesktop.msi"
+ --exe .\setup-redist.exe --license .\LICENSE.md --out-file .\khmer.exe
+```
+
+#### Bash (Linux, WSL, macOS, etc)
+
+```shell
+kmc build windows-package-installer \
+ ./khmer_angkor/source/khmer_angkor.kps \
+ --msi ./redist/keymandesktop.msi \
+ --exe ./redist/setup-redist.exe \
+ --license ./redist/LICENSE.md \
+ --out-file ./khmer.exe
+```
+
+Note: paths shown above may vary.
+
+## `kmc analyze osk-char-use` options
+
+`-b, --base`
+
+: First PUA codepoint to use, in hexadecimal (default F100)
+
+`--include-counts`
+
+: Include number of times each character is referenced (default: false)
+
+`--strip-dotted-circle`
+
+: Strip U+25CC (dotted circle base) from results (default: false)
+
+`-m, --mapping-file `
+
+: Result file to write to (.json, .md, or .txt)
+
+For more information on the purpose of `analyze osk-char-use` and
+`analyze rewrite-osk-from-char-use`, see
+[`&displayMap`](/developer/language/reference/displaymap).
+
+## `kmc analyze osk-rewrite-from-char-use` options
+
+`-m, --mapping-file `
+
+: JSON mapping file to read from.
+
+For more information on the purpose of `analyze osk-char-use` and
+`analyze rewrite-osk-from-char-use`, see
+[`&displayMap`](/developer/language/reference/displaymap).
+
+## `kmc message` options
+
+One or more message identifiers can be specified for text or json formats.
+
+Message identifiers are 5 hex digit codes, optonally preceded by `KM`, for
+example `KM02001`.
+
+For Markdown format, all messages are always emitted, to `out-path`, and no
+messages can be specified on the command line, with one file per message, and
+index files also generated. The Markdown mode is used to generate the online
+documentation on help.keyman.com.
+
+`-f, --format `
+
+: Output format, one of:
+ * `text`: plain text output to console or file, of specified messages
+ * `json`: JSON formatted to console or file, of specified messages
+ * `markdown`: Markdown formatted text output, to a folder, of all messages
+
+`-o, --out-path `
+
+: Output folder name for Markdown format (required for Markdown), or optional
+ output filename for text and json formats.
+
+`-a, --all-messages`
+
+: Emit descriptions for all messages (text, json formats)
+
diff --git a/developer/docs/help/reference/kmc/index.md b/developer/docs/help/reference/kmc/index.md
new file mode 100644
index 00000000000..1630554982b
--- /dev/null
+++ b/developer/docs/help/reference/kmc/index.md
@@ -0,0 +1,15 @@
+---
+title: Command line compiler kmc
+---
+
+kmc is the Keyman Developer command line compiler interface.
+
+# Using kmc
+
+* [Get started using kmc](cli/get-started)
+* [Migrating from kmcomp to kmc](cli/kmcomp-migration)
+
+# kmc Reference
+
+* [kmc Command Line Reference](cli/reference)
+* [kmc module API Reference](api)
diff --git a/developer/docs/help/reference/messages/common-types.commontypesmessages.md b/developer/docs/help/reference/messages/common-types.commontypesmessages.md
new file mode 100644
index 00000000000..38baeb6e522
--- /dev/null
+++ b/developer/docs/help/reference/messages/common-types.commontypesmessages.md
@@ -0,0 +1,13 @@
+---
+title: Compiler Messages Reference for @keymanapp/common-types
+---
+
+ Code | Identifier | Message
+------|------------|---------
+[KM01001](km01001) | `ERROR_SchemaValidationError` | Error validating LDML XML file: <param>: <param>: <param> <param>
+[KM01002](km01002) | `ERROR_ImportInvalidBase` | Import element with base <param> is unsupported\. Only cldr is supported\.
+[KM01003](km01003) | `ERROR_ImportInvalidPath` | Import element with invalid path <param>: expected the form '45/\*\.xml
+[KM01004](km01004) | `ERROR_ImportReadFail` | Import could not read data with path <param>: expected the form '45/\*\.xml'
+[KM01005](km01005) | `ERROR_ImportWrongRoot` | Invalid import file <param>: expected <param> as root element\.
+[KM01006](km01006) | `ERROR_ImportMergeFail` | Problem importing <param>: not sure how to handle non\-array <param>\.<param>
+[KM01007](km01007) | `ERROR_TestDataUnexpectedArray` | Problem reading test data: expected single <param> element, found multiple
diff --git a/developer/docs/help/reference/messages/index.md b/developer/docs/help/reference/messages/index.md
new file mode 100644
index 00000000000..907ca597f65
--- /dev/null
+++ b/developer/docs/help/reference/messages/index.md
@@ -0,0 +1,14 @@
+---
+title: Compiler Messages Reference
+---
+
+* [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages)
+* [common-types.CommonTypesMessages](common-types.commontypesmessages)
+* [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages)
+* [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages)
+* [kmc-package.CompilerMessages](kmc-package.compilermessages)
+* [kmc.InfrastructureMessages](kmc.infrastructuremessages)
+* [kmc-analyze.AnalyzerMessages](kmc-analyze.analyzermessages)
+* [kmc-kmn.KmwCompilerMessages](kmc-kmn.kmwcompilermessages)
+* [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages)
+* [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages)
diff --git a/developer/docs/help/reference/messages/km00001.md b/developer/docs/help/reference/messages/km00001.md
new file mode 100644
index 00000000000..0a904083131
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00001.md
@@ -0,0 +1,11 @@
+---
+title: KM00001: HINT_NormalizationDisabled
+---
+
+| | |
+|------------|---------- |
+| Message | normalization=disabled is not recommended\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `HINT_NormalizationDisabled` |
+
+
diff --git a/developer/docs/help/reference/messages/km00002.md b/developer/docs/help/reference/messages/km00002.md
new file mode 100644
index 00000000000..8d40e1af756
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00002.md
@@ -0,0 +1,11 @@
+---
+title: KM00002: ERROR_InvalidLocale
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid BCP 47 locale form '<param>' |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_InvalidLocale` |
+
+
diff --git a/developer/docs/help/reference/messages/km00003.md b/developer/docs/help/reference/messages/km00003.md
new file mode 100644
index 00000000000..10d25857963
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00003.md
@@ -0,0 +1,11 @@
+---
+title: KM00003: ERROR_HardwareLayerHasTooManyRows
+---
+
+| | |
+|------------|---------- |
+| Message | 'hardware' layer has too many rows |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_HardwareLayerHasTooManyRows` |
+
+
diff --git a/developer/docs/help/reference/messages/km00004.md b/developer/docs/help/reference/messages/km00004.md
new file mode 100644
index 00000000000..662a2b0fcd3
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00004.md
@@ -0,0 +1,11 @@
+---
+title: KM00004: ERROR_RowOnHardwareLayerHasTooManyKeys
+---
+
+| | |
+|------------|---------- |
+| Message | Row \#<param> on 'hardware' <param> layer for modifier none has too many keys |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_RowOnHardwareLayerHasTooManyKeys` |
+
+
diff --git a/developer/docs/help/reference/messages/km00005.md b/developer/docs/help/reference/messages/km00005.md
new file mode 100644
index 00000000000..1f6a6a2573b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00005.md
@@ -0,0 +1,11 @@
+---
+title: KM00005: ERROR_KeyNotFoundInKeyBag
+---
+
+| | |
+|------------|---------- |
+| Message | Key '<param>' in position \#<param> on row \#<param> of layer <param>, form '<param>' not found in key bag |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_KeyNotFoundInKeyBag` |
+
+
diff --git a/developer/docs/help/reference/messages/km00006.md b/developer/docs/help/reference/messages/km00006.md
new file mode 100644
index 00000000000..93fe0a33099
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00006.md
@@ -0,0 +1,11 @@
+---
+title: KM00006: HINT_OneOrMoreRepeatedLocales
+---
+
+| | |
+|------------|---------- |
+| Message | After minimization, one or more locales is repeated and has been removed |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `HINT_OneOrMoreRepeatedLocales` |
+
+
diff --git a/developer/docs/help/reference/messages/km00007.md b/developer/docs/help/reference/messages/km00007.md
new file mode 100644
index 00000000000..8199e9ba278
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00007.md
@@ -0,0 +1,11 @@
+---
+title: KM00007: ERROR_InvalidFile
+---
+
+| | |
+|------------|---------- |
+| Message | The source file has an invalid structure: <param> |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_InvalidFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km00008.md b/developer/docs/help/reference/messages/km00008.md
new file mode 100644
index 00000000000..11fa5bba0ef
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00008.md
@@ -0,0 +1,11 @@
+---
+title: KM00008: HINT_LocaleIsNotMinimalAndClean
+---
+
+| | |
+|------------|---------- |
+| Message | Locale '<param>' is not minimal or correctly formatted and should be '<param>' |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `HINT_LocaleIsNotMinimalAndClean` |
+
+
diff --git a/developer/docs/help/reference/messages/km00009.md b/developer/docs/help/reference/messages/km00009.md
new file mode 100644
index 00000000000..144f9049916
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00009.md
@@ -0,0 +1,11 @@
+---
+title: KM00009: ERROR_InvalidScanCode
+---
+
+| | |
+|------------|---------- |
+| Message | Form '<param>' has invalid/unknown scancodes '<param>' |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_InvalidScanCode` |
+
+
diff --git a/developer/docs/help/reference/messages/km0000a.md b/developer/docs/help/reference/messages/km0000a.md
new file mode 100644
index 00000000000..7396bd7e0f2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0000a.md
@@ -0,0 +1,11 @@
+---
+title: KM0000A: WARN_CustomForm
+---
+
+| | |
+|------------|---------- |
+| Message | Custom <form id="<param>"> element\. Key layout may not be as expected\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `WARN_CustomForm` |
+
+
diff --git a/developer/docs/help/reference/messages/km0000b.md b/developer/docs/help/reference/messages/km0000b.md
new file mode 100644
index 00000000000..0fd4457bec6
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0000b.md
@@ -0,0 +1,11 @@
+---
+title: KM0000B: ERROR_GestureKeyNotFoundInKeyBag
+---
+
+| | |
+|------------|---------- |
+| Message | Key '<param>' not found in key bag, referenced from other '<param>' in <param> |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_GestureKeyNotFoundInKeyBag` |
+
+
diff --git a/developer/docs/help/reference/messages/km0000d.md b/developer/docs/help/reference/messages/km0000d.md
new file mode 100644
index 00000000000..7ab2b13d3cc
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0000d.md
@@ -0,0 +1,11 @@
+---
+title: KM0000D: ERROR_InvalidVersion
+---
+
+| | |
+|------------|---------- |
+| Message | Version number '<param>' must be a semantic version format string\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_InvalidVersion` |
+
+
diff --git a/developer/docs/help/reference/messages/km0000e.md b/developer/docs/help/reference/messages/km0000e.md
new file mode 100644
index 00000000000..e14df1843cc
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0000e.md
@@ -0,0 +1,11 @@
+---
+title: KM0000E: ERROR_MustBeAtLeastOneLayerElement
+---
+
+| | |
+|------------|---------- |
+| Message | The source file must contain at least one layer element\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_MustBeAtLeastOneLayerElement` |
+
+
diff --git a/developer/docs/help/reference/messages/km00010.md b/developer/docs/help/reference/messages/km00010.md
new file mode 100644
index 00000000000..9f0dfbab729
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00010.md
@@ -0,0 +1,11 @@
+---
+title: KM00010: ERROR_DisplayIsRepeated
+---
+
+| | |
+|------------|---------- |
+| Message | display has more than one display entry\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_DisplayIsRepeated` |
+
+
diff --git a/developer/docs/help/reference/messages/km00011.md b/developer/docs/help/reference/messages/km00011.md
new file mode 100644
index 00000000000..6f26e3ecf87
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00011.md
@@ -0,0 +1,11 @@
+---
+title: KM00011: ERROR_KeyMissingToGapOrSwitch
+---
+
+| | |
+|------------|---------- |
+| Message | key id='<param>' must have either output=, gap=, or layerId=\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_KeyMissingToGapOrSwitch` |
+
+
diff --git a/developer/docs/help/reference/messages/km00012.md b/developer/docs/help/reference/messages/km00012.md
new file mode 100644
index 00000000000..6f2789649d0
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00012.md
@@ -0,0 +1,11 @@
+---
+title: KM00012: ERROR_ExcessHardware
+---
+
+| | |
+|------------|---------- |
+| Message | layers formId=<param>: Can only have one non\-'touch' element |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_ExcessHardware` |
+
+
diff --git a/developer/docs/help/reference/messages/km00013.md b/developer/docs/help/reference/messages/km00013.md
new file mode 100644
index 00000000000..4795d8683c7
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00013.md
@@ -0,0 +1,11 @@
+---
+title: KM00013: ERROR_InvalidHardware
+---
+
+| | |
+|------------|---------- |
+| Message | layers has invalid value formId=<param> |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_InvalidHardware` |
+
+
diff --git a/developer/docs/help/reference/messages/km00014.md b/developer/docs/help/reference/messages/km00014.md
new file mode 100644
index 00000000000..64d65a96da0
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00014.md
@@ -0,0 +1,11 @@
+---
+title: KM00014: ERROR_InvalidModifier
+---
+
+| | |
+|------------|---------- |
+| Message | layer has invalid modifiers='<param>' on layer id=<param> |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_InvalidModifier` |
+
+
diff --git a/developer/docs/help/reference/messages/km00015.md b/developer/docs/help/reference/messages/km00015.md
new file mode 100644
index 00000000000..1102e70a94b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00015.md
@@ -0,0 +1,11 @@
+---
+title: KM00015: ERROR_MissingFlicks
+---
+
+| | |
+|------------|---------- |
+| Message | key id=<param> refers to missing flickId=<param> |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_MissingFlicks` |
+
+
diff --git a/developer/docs/help/reference/messages/km00016.md b/developer/docs/help/reference/messages/km00016.md
new file mode 100644
index 00000000000..b6c3a6afb14
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00016.md
@@ -0,0 +1,11 @@
+---
+title: KM00016: ERROR_DuplicateVariable
+---
+
+| | |
+|------------|---------- |
+| Message | duplicate variables: id=<param> |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_DuplicateVariable` |
+
+
diff --git a/developer/docs/help/reference/messages/km00018.md b/developer/docs/help/reference/messages/km00018.md
new file mode 100644
index 00000000000..a323ee5c648
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00018.md
@@ -0,0 +1,11 @@
+---
+title: KM00018: ERROR_InvalidTransformsType
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid transforms types: '<param>' |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_InvalidTransformsType` |
+
+
diff --git a/developer/docs/help/reference/messages/km00019.md b/developer/docs/help/reference/messages/km00019.md
new file mode 100644
index 00000000000..21f25ec3c00
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00019.md
@@ -0,0 +1,11 @@
+---
+title: KM00019: ERROR_DuplicateTransformsType
+---
+
+| | |
+|------------|---------- |
+| Message | Duplicate transforms types: '<param>' |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_DuplicateTransformsType` |
+
+
diff --git a/developer/docs/help/reference/messages/km0001a.md b/developer/docs/help/reference/messages/km0001a.md
new file mode 100644
index 00000000000..5d2eae21429
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0001a.md
@@ -0,0 +1,11 @@
+---
+title: KM0001A: ERROR_MixedTransformGroup
+---
+
+| | |
+|------------|---------- |
+| Message | transformGroup cannot contain both reorder and transform elements |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_MixedTransformGroup` |
+
+
diff --git a/developer/docs/help/reference/messages/km0001b.md b/developer/docs/help/reference/messages/km0001b.md
new file mode 100644
index 00000000000..8ddb410a580
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0001b.md
@@ -0,0 +1,11 @@
+---
+title: KM0001B: ERROR_EmptyTransformGroup
+---
+
+| | |
+|------------|---------- |
+| Message | transformGroup must have either reorder or transform elements |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_EmptyTransformGroup` |
+
+
diff --git a/developer/docs/help/reference/messages/km0001c.md b/developer/docs/help/reference/messages/km0001c.md
new file mode 100644
index 00000000000..3338ca0251a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0001c.md
@@ -0,0 +1,11 @@
+---
+title: KM0001C: ERROR_MissingStringVariable
+---
+
+| | |
+|------------|---------- |
+| Message | Reference to undefined string variable: $\{<param>\} |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_MissingStringVariable` |
+
+
diff --git a/developer/docs/help/reference/messages/km0001d.md b/developer/docs/help/reference/messages/km0001d.md
new file mode 100644
index 00000000000..d24fa058f93
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0001d.md
@@ -0,0 +1,11 @@
+---
+title: KM0001D: ERROR_MissingSetVariable
+---
+
+| | |
+|------------|---------- |
+| Message | Reference to undefined set variable: $\[<param>\] |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_MissingSetVariable` |
+
+
diff --git a/developer/docs/help/reference/messages/km0001e.md b/developer/docs/help/reference/messages/km0001e.md
new file mode 100644
index 00000000000..591f53d60ae
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0001e.md
@@ -0,0 +1,11 @@
+---
+title: KM0001E: ERROR_MissingUnicodeSetVariable
+---
+
+| | |
+|------------|---------- |
+| Message | Reference to undefined UnicodeSet variable: $\[<param>\] |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_MissingUnicodeSetVariable` |
+
+
diff --git a/developer/docs/help/reference/messages/km0001f.md b/developer/docs/help/reference/messages/km0001f.md
new file mode 100644
index 00000000000..03ba8a63ee4
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0001f.md
@@ -0,0 +1,11 @@
+---
+title: KM0001F: ERROR_NeedSpacesBetweenSetVariables
+---
+
+| | |
+|------------|---------- |
+| Message | Need spaces between set variables: <param> |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_NeedSpacesBetweenSetVariables` |
+
+
diff --git a/developer/docs/help/reference/messages/km00020.md b/developer/docs/help/reference/messages/km00020.md
new file mode 100644
index 00000000000..fa31310c59f
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00020.md
@@ -0,0 +1,11 @@
+---
+title: KM00020: ERROR_CantReferenceSetFromUnicodeSet
+---
+
+| | |
+|------------|---------- |
+| Message | Illegal use of set variable from within UnicodeSet: $\[<param>\] |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_CantReferenceSetFromUnicodeSet` |
+
+
diff --git a/developer/docs/help/reference/messages/km00021.md b/developer/docs/help/reference/messages/km00021.md
new file mode 100644
index 00000000000..7a51803a081
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00021.md
@@ -0,0 +1,11 @@
+---
+title: KM00021: ERROR_MissingMarkers
+---
+
+| | |
+|------------|---------- |
+| Message | Markers used for matching but not defined: <param> |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_MissingMarkers` |
+
+
diff --git a/developer/docs/help/reference/messages/km00022.md b/developer/docs/help/reference/messages/km00022.md
new file mode 100644
index 00000000000..df332561348
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00022.md
@@ -0,0 +1,11 @@
+---
+title: KM00022: ERROR_DisplayNeedsToOrId
+---
+
+| | |
+|------------|---------- |
+| Message | display needs output= or keyId=, but not both |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_DisplayNeedsToOrId` |
+
+
diff --git a/developer/docs/help/reference/messages/km00023.md b/developer/docs/help/reference/messages/km00023.md
new file mode 100644
index 00000000000..358ea1c1ef4
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00023.md
@@ -0,0 +1,11 @@
+---
+title: KM00023: HINT_PUACharacters
+---
+
+| | |
+|------------|---------- |
+| Message | File contains <param> PUA character\(s\), including Illegal \(U\+NAN\) |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `HINT_PUACharacters` |
+
+
diff --git a/developer/docs/help/reference/messages/km00024.md b/developer/docs/help/reference/messages/km00024.md
new file mode 100644
index 00000000000..262df5019ea
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00024.md
@@ -0,0 +1,11 @@
+---
+title: KM00024: WARN_UnassignedCharacters
+---
+
+| | |
+|------------|---------- |
+| Message | File contains <param> unassigned character\(s\), including Illegal \(U\+NAN\) |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `WARN_UnassignedCharacters` |
+
+
diff --git a/developer/docs/help/reference/messages/km00025.md b/developer/docs/help/reference/messages/km00025.md
new file mode 100644
index 00000000000..e3946bbc528
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00025.md
@@ -0,0 +1,11 @@
+---
+title: KM00025: ERROR_IllegalCharacters
+---
+
+| | |
+|------------|---------- |
+| Message | File contains <param> illegal character\(s\), including Illegal \(U\+NAN\) |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_IllegalCharacters` |
+
+
diff --git a/developer/docs/help/reference/messages/km00026.md b/developer/docs/help/reference/messages/km00026.md
new file mode 100644
index 00000000000..ebeeed31772
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00026.md
@@ -0,0 +1,11 @@
+---
+title: KM00026: HINT_CharClassImplicitDenorm
+---
+
+| | |
+|------------|---------- |
+| Message | File has character classes which span non\-NFD character\(s\), including Illegal \(U\+NAN\)\. These will not match any text\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `HINT_CharClassImplicitDenorm` |
+
+
diff --git a/developer/docs/help/reference/messages/km00027.md b/developer/docs/help/reference/messages/km00027.md
new file mode 100644
index 00000000000..990b9b01a3c
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00027.md
@@ -0,0 +1,11 @@
+---
+title: KM00027: WARN_CharClassExplicitDenorm
+---
+
+| | |
+|------------|---------- |
+| Message | File has character classes which include non\-NFD characters\(s\), including Illegal \(U\+NAN\)\. These will not match any text\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `WARN_CharClassExplicitDenorm` |
+
+
diff --git a/developer/docs/help/reference/messages/km00028.md b/developer/docs/help/reference/messages/km00028.md
new file mode 100644
index 00000000000..5f46870d57a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00028.md
@@ -0,0 +1,11 @@
+---
+title: KM00028: ERROR_UnparseableReorderSet
+---
+
+| | |
+|------------|---------- |
+| Message | Illegal UnicodeSet "<param>" in reorder "<param> |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_UnparseableReorderSet` |
+
+
diff --git a/developer/docs/help/reference/messages/km00030.md b/developer/docs/help/reference/messages/km00030.md
new file mode 100644
index 00000000000..43eba550c7c
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00030.md
@@ -0,0 +1,11 @@
+---
+title: KM00030: ERROR_InvalidQuadEscape
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid escape "\\u0000"\. Hint: Use "\\u\{<param>\}" |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_InvalidQuadEscape` |
+
+
diff --git a/developer/docs/help/reference/messages/km00f00.md b/developer/docs/help/reference/messages/km00f00.md
new file mode 100644
index 00000000000..6077048b531
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00f00.md
@@ -0,0 +1,11 @@
+---
+title: KM00F00: ERROR_UnparseableTransformFrom
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid transform from="<param>": "<param>" |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_UnparseableTransformFrom` |
+
+
diff --git a/developer/docs/help/reference/messages/km00f01.md b/developer/docs/help/reference/messages/km00f01.md
new file mode 100644
index 00000000000..f2af03199ce
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00f01.md
@@ -0,0 +1,11 @@
+---
+title: KM00F01: ERROR_IllegalTransformDollarsign
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid transform from="<param>": Unescaped dollar\-sign \($\) is not valid transform syntax\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_IllegalTransformDollarsign` |
+
+**Hint**: Use `\$` to match a literal dollar-sign.
diff --git a/developer/docs/help/reference/messages/km00f02.md b/developer/docs/help/reference/messages/km00f02.md
new file mode 100644
index 00000000000..51cbc4b4521
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00f02.md
@@ -0,0 +1,11 @@
+---
+title: KM00F02: ERROR_TransformFromMatchesNothing
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid transfom from="<param>": Matches an empty string\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_TransformFromMatchesNothing` |
+
+
diff --git a/developer/docs/help/reference/messages/km00f03.md b/developer/docs/help/reference/messages/km00f03.md
new file mode 100644
index 00000000000..ac4f22bc7d7
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00f03.md
@@ -0,0 +1,11 @@
+---
+title: KM00F03: ERROR_IllegalTransformPlus
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid transform from="<param>": Unescaped plus \(\+\) is not valid transform syntax\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_IllegalTransformPlus` |
+
+**Hint**: Use `\+` to match a literal plus.
diff --git a/developer/docs/help/reference/messages/km00f04.md b/developer/docs/help/reference/messages/km00f04.md
new file mode 100644
index 00000000000..2132866bd87
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00f04.md
@@ -0,0 +1,11 @@
+---
+title: KM00F04: ERROR_IllegalTransformAsterisk
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid transform from="<param>": Unescaped asterisk \(\*\) is not valid transform syntax\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_IllegalTransformAsterisk` |
+
+**Hint**: Use `\*` to match a literal asterisk.
diff --git a/developer/docs/help/reference/messages/km01001.md b/developer/docs/help/reference/messages/km01001.md
new file mode 100644
index 00000000000..a4440ecb017
--- /dev/null
+++ b/developer/docs/help/reference/messages/km01001.md
@@ -0,0 +1,11 @@
+---
+title: KM01001: ERROR_SchemaValidationError
+---
+
+| | |
+|------------|---------- |
+| Message | Error validating LDML XML file: <param>: <param>: <param> <param> |
+| Module | [common-types.CommonTypesMessages](common-types.commontypesmessages) |
+| Identifier | `ERROR_SchemaValidationError` |
+
+
diff --git a/developer/docs/help/reference/messages/km01002.md b/developer/docs/help/reference/messages/km01002.md
new file mode 100644
index 00000000000..63338ff9775
--- /dev/null
+++ b/developer/docs/help/reference/messages/km01002.md
@@ -0,0 +1,11 @@
+---
+title: KM01002: ERROR_ImportInvalidBase
+---
+
+| | |
+|------------|---------- |
+| Message | Import element with base <param> is unsupported\. Only cldr is supported\. |
+| Module | [common-types.CommonTypesMessages](common-types.commontypesmessages) |
+| Identifier | `ERROR_ImportInvalidBase` |
+
+
diff --git a/developer/docs/help/reference/messages/km01003.md b/developer/docs/help/reference/messages/km01003.md
new file mode 100644
index 00000000000..a8ce37a80e0
--- /dev/null
+++ b/developer/docs/help/reference/messages/km01003.md
@@ -0,0 +1,11 @@
+---
+title: KM01003: ERROR_ImportInvalidPath
+---
+
+| | |
+|------------|---------- |
+| Message | Import element with invalid path <param>: expected the form '45/\*\.xml |
+| Module | [common-types.CommonTypesMessages](common-types.commontypesmessages) |
+| Identifier | `ERROR_ImportInvalidPath` |
+
+
diff --git a/developer/docs/help/reference/messages/km01004.md b/developer/docs/help/reference/messages/km01004.md
new file mode 100644
index 00000000000..279786f9b6d
--- /dev/null
+++ b/developer/docs/help/reference/messages/km01004.md
@@ -0,0 +1,11 @@
+---
+title: KM01004: ERROR_ImportReadFail
+---
+
+| | |
+|------------|---------- |
+| Message | Import could not read data with path <param>: expected the form '45/\*\.xml' |
+| Module | [common-types.CommonTypesMessages](common-types.commontypesmessages) |
+| Identifier | `ERROR_ImportReadFail` |
+
+
diff --git a/developer/docs/help/reference/messages/km01005.md b/developer/docs/help/reference/messages/km01005.md
new file mode 100644
index 00000000000..2d032c4ed5c
--- /dev/null
+++ b/developer/docs/help/reference/messages/km01005.md
@@ -0,0 +1,11 @@
+---
+title: KM01005: ERROR_ImportWrongRoot
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid import file <param>: expected <param> as root element\. |
+| Module | [common-types.CommonTypesMessages](common-types.commontypesmessages) |
+| Identifier | `ERROR_ImportWrongRoot` |
+
+
diff --git a/developer/docs/help/reference/messages/km01006.md b/developer/docs/help/reference/messages/km01006.md
new file mode 100644
index 00000000000..1e04d8c403c
--- /dev/null
+++ b/developer/docs/help/reference/messages/km01006.md
@@ -0,0 +1,11 @@
+---
+title: KM01006: ERROR_ImportMergeFail
+---
+
+| | |
+|------------|---------- |
+| Message | Problem importing <param>: not sure how to handle non\-array <param>\.<param> |
+| Module | [common-types.CommonTypesMessages](common-types.commontypesmessages) |
+| Identifier | `ERROR_ImportMergeFail` |
+
+
diff --git a/developer/docs/help/reference/messages/km01007.md b/developer/docs/help/reference/messages/km01007.md
new file mode 100644
index 00000000000..e9a0dc89f77
--- /dev/null
+++ b/developer/docs/help/reference/messages/km01007.md
@@ -0,0 +1,11 @@
+---
+title: KM01007: ERROR_TestDataUnexpectedArray
+---
+
+| | |
+|------------|---------- |
+| Message | Problem reading test data: expected single <param> element, found multiple |
+| Module | [common-types.CommonTypesMessages](common-types.commontypesmessages) |
+| Identifier | `ERROR_TestDataUnexpectedArray` |
+
+
diff --git a/developer/docs/help/reference/messages/km02001.md b/developer/docs/help/reference/messages/km02001.md
new file mode 100644
index 00000000000..4532a816790
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02001.md
@@ -0,0 +1,11 @@
+---
+title: KM02001: INFO_EndOfFile
+---
+
+| | |
+|------------|---------- |
+| Message | \(no error \- reserved code\) |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `INFO_EndOfFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km02002.md b/developer/docs/help/reference/messages/km02002.md
new file mode 100644
index 00000000000..3b346a77449
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02002.md
@@ -0,0 +1,11 @@
+---
+title: KM02002: FATAL_BadCallParams
+---
+
+| | |
+|------------|---------- |
+| Message | CompileKeyboardFile was called with bad parameters |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_BadCallParams` |
+
+
diff --git a/developer/docs/help/reference/messages/km02004.md b/developer/docs/help/reference/messages/km02004.md
new file mode 100644
index 00000000000..c583d5ef786
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02004.md
@@ -0,0 +1,11 @@
+---
+title: KM02004: FATAL_CannotAllocateMemory
+---
+
+| | |
+|------------|---------- |
+| Message | Out of memory |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_CannotAllocateMemory` |
+
+
diff --git a/developer/docs/help/reference/messages/km02005.md b/developer/docs/help/reference/messages/km02005.md
new file mode 100644
index 00000000000..4713ec22cc4
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02005.md
@@ -0,0 +1,11 @@
+---
+title: KM02005: ERROR_InfileNotExist
+---
+
+| | |
+|------------|---------- |
+| Message | Cannot find the input file |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InfileNotExist` |
+
+
diff --git a/developer/docs/help/reference/messages/km02007.md b/developer/docs/help/reference/messages/km02007.md
new file mode 100644
index 00000000000..4690c3c53e7
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02007.md
@@ -0,0 +1,11 @@
+---
+title: KM02007: FATAL_UnableToWriteFully
+---
+
+| | |
+|------------|---------- |
+| Message | Unable to write the file completely |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_UnableToWriteFully` |
+
+
diff --git a/developer/docs/help/reference/messages/km02008.md b/developer/docs/help/reference/messages/km02008.md
new file mode 100644
index 00000000000..31eddb1e375
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02008.md
@@ -0,0 +1,11 @@
+---
+title: KM02008: ERROR_CannotReadInfile
+---
+
+| | |
+|------------|---------- |
+| Message | Cannot read the input file |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CannotReadInfile` |
+
+
diff --git a/developer/docs/help/reference/messages/km02009.md b/developer/docs/help/reference/messages/km02009.md
new file mode 100644
index 00000000000..d1e3ec2155b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02009.md
@@ -0,0 +1,11 @@
+---
+title: KM02009: FATAL_SomewhereIGotItWrong
+---
+
+| | |
+|------------|---------- |
+| Message | Internal error: contact Keyman |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_SomewhereIGotItWrong` |
+
+
diff --git a/developer/docs/help/reference/messages/km0200a.md b/developer/docs/help/reference/messages/km0200a.md
new file mode 100644
index 00000000000..c75422a406a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0200a.md
@@ -0,0 +1,11 @@
+---
+title: KM0200A: ERROR_InvalidToken
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid token found |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidToken` |
+
+
diff --git a/developer/docs/help/reference/messages/km0200b.md b/developer/docs/help/reference/messages/km0200b.md
new file mode 100644
index 00000000000..41e821d0184
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0200b.md
@@ -0,0 +1,11 @@
+---
+title: KM0200B: ERROR_InvalidBegin
+---
+
+| | |
+|------------|---------- |
+| Message | A "begin unicode" statement is required to compile a KeymanWeb keyboard |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidBegin` |
+
+
diff --git a/developer/docs/help/reference/messages/km0200c.md b/developer/docs/help/reference/messages/km0200c.md
new file mode 100644
index 00000000000..8567272ba11
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0200c.md
@@ -0,0 +1,11 @@
+---
+title: KM0200C: ERROR_InvalidName
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'name' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidName` |
+
+
diff --git a/developer/docs/help/reference/messages/km0200d.md b/developer/docs/help/reference/messages/km0200d.md
new file mode 100644
index 00000000000..41ac2e2b613
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0200d.md
@@ -0,0 +1,11 @@
+---
+title: KM0200D: ERROR_InvalidVersion
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'version' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidVersion` |
+
+
diff --git a/developer/docs/help/reference/messages/km0200e.md b/developer/docs/help/reference/messages/km0200e.md
new file mode 100644
index 00000000000..0853617cc4a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0200e.md
@@ -0,0 +1,11 @@
+---
+title: KM0200E: ERROR_InvalidLanguageLine
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'language' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidLanguageLine` |
+
+
diff --git a/developer/docs/help/reference/messages/km0200f.md b/developer/docs/help/reference/messages/km0200f.md
new file mode 100644
index 00000000000..f894cd6f49b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0200f.md
@@ -0,0 +1,11 @@
+---
+title: KM0200F: ERROR_LayoutButNoLanguage
+---
+
+| | |
+|------------|---------- |
+| Message | Layout command found but no language command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_LayoutButNoLanguage` |
+
+
diff --git a/developer/docs/help/reference/messages/km02010.md b/developer/docs/help/reference/messages/km02010.md
new file mode 100644
index 00000000000..b5cb984cf47
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02010.md
@@ -0,0 +1,11 @@
+---
+title: KM02010: ERROR_InvalidLayoutLine
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'layout' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidLayoutLine` |
+
+
diff --git a/developer/docs/help/reference/messages/km02011.md b/developer/docs/help/reference/messages/km02011.md
new file mode 100644
index 00000000000..07658b237ae
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02011.md
@@ -0,0 +1,11 @@
+---
+title: KM02011: ERROR_NoVersionLine
+---
+
+| | |
+|------------|---------- |
+| Message | No version line found for file |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_NoVersionLine` |
+
+
diff --git a/developer/docs/help/reference/messages/km02012.md b/developer/docs/help/reference/messages/km02012.md
new file mode 100644
index 00000000000..fa208fab3ea
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02012.md
@@ -0,0 +1,11 @@
+---
+title: KM02012: ERROR_InvalidGroupLine
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'group' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidGroupLine` |
+
+
diff --git a/developer/docs/help/reference/messages/km02013.md b/developer/docs/help/reference/messages/km02013.md
new file mode 100644
index 00000000000..924407d9e97
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02013.md
@@ -0,0 +1,11 @@
+---
+title: KM02013: ERROR_InvalidStoreLine
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'store' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidStoreLine` |
+
+
diff --git a/developer/docs/help/reference/messages/km02014.md b/developer/docs/help/reference/messages/km02014.md
new file mode 100644
index 00000000000..6d010dc536d
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02014.md
@@ -0,0 +1,11 @@
+---
+title: KM02014: ERROR_InvalidCodeInKeyPartOfRule
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid command or code found in key part of rule |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidCodeInKeyPartOfRule` |
+
+
diff --git a/developer/docs/help/reference/messages/km02015.md b/developer/docs/help/reference/messages/km02015.md
new file mode 100644
index 00000000000..01006037b26
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02015.md
@@ -0,0 +1,11 @@
+---
+title: KM02015: ERROR_InvalidDeadkey
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'deadkey' or 'dk' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidDeadkey` |
+
+
diff --git a/developer/docs/help/reference/messages/km02016.md b/developer/docs/help/reference/messages/km02016.md
new file mode 100644
index 00000000000..603122cd62d
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02016.md
@@ -0,0 +1,11 @@
+---
+title: KM02016: ERROR_InvalidValue
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid value in extended string |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidValue` |
+
+
diff --git a/developer/docs/help/reference/messages/km02017.md b/developer/docs/help/reference/messages/km02017.md
new file mode 100644
index 00000000000..74a0a5c5dfc
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02017.md
@@ -0,0 +1,11 @@
+---
+title: KM02017: ERROR_ZeroLengthString
+---
+
+| | |
+|------------|---------- |
+| Message | A string of zero characters was found |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_ZeroLengthString` |
+
+
diff --git a/developer/docs/help/reference/messages/km02018.md b/developer/docs/help/reference/messages/km02018.md
new file mode 100644
index 00000000000..662e2a7b544
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02018.md
@@ -0,0 +1,11 @@
+---
+title: KM02018: ERROR_TooManyIndexToKeyRefs
+---
+
+| | |
+|------------|---------- |
+| Message | Too many index commands refering to key string |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_TooManyIndexToKeyRefs` |
+
+
diff --git a/developer/docs/help/reference/messages/km02019.md b/developer/docs/help/reference/messages/km02019.md
new file mode 100644
index 00000000000..6ff35e690a6
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02019.md
@@ -0,0 +1,11 @@
+---
+title: KM02019: ERROR_UnterminatedString
+---
+
+| | |
+|------------|---------- |
+| Message | Unterminated string in line |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_UnterminatedString` |
+
+
diff --git a/developer/docs/help/reference/messages/km0201a.md b/developer/docs/help/reference/messages/km0201a.md
new file mode 100644
index 00000000000..c5be97b8103
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0201a.md
@@ -0,0 +1,11 @@
+---
+title: KM0201A: ERROR_StringInVirtualKeySection
+---
+
+| | |
+|------------|---------- |
+| Message | extend string illegal in virtual key section |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_StringInVirtualKeySection` |
+
+
diff --git a/developer/docs/help/reference/messages/km0201b.md b/developer/docs/help/reference/messages/km0201b.md
new file mode 100644
index 00000000000..142b6b5f9a0
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0201b.md
@@ -0,0 +1,11 @@
+---
+title: KM0201B: ERROR_AnyInVirtualKeySection
+---
+
+| | |
+|------------|---------- |
+| Message | 'any' command is illegal in virtual key section |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_AnyInVirtualKeySection` |
+
+
diff --git a/developer/docs/help/reference/messages/km0201c.md b/developer/docs/help/reference/messages/km0201c.md
new file mode 100644
index 00000000000..353337f6789
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0201c.md
@@ -0,0 +1,11 @@
+---
+title: KM0201C: ERROR_InvalidAny
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'any' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidAny` |
+
+
diff --git a/developer/docs/help/reference/messages/km0201d.md b/developer/docs/help/reference/messages/km0201d.md
new file mode 100644
index 00000000000..e998ebfd8f8
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0201d.md
@@ -0,0 +1,11 @@
+---
+title: KM0201D: ERROR_StoreDoesNotExist
+---
+
+| | |
+|------------|---------- |
+| Message | Store referenced does not exist |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_StoreDoesNotExist` |
+
+
diff --git a/developer/docs/help/reference/messages/km0201e.md b/developer/docs/help/reference/messages/km0201e.md
new file mode 100644
index 00000000000..f6af856370a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0201e.md
@@ -0,0 +1,11 @@
+---
+title: KM0201E: ERROR_BeepInVirtualKeySection
+---
+
+| | |
+|------------|---------- |
+| Message | 'beep' command is illegal in virtual key section |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_BeepInVirtualKeySection` |
+
+
diff --git a/developer/docs/help/reference/messages/km0201f.md b/developer/docs/help/reference/messages/km0201f.md
new file mode 100644
index 00000000000..30c1123411b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0201f.md
@@ -0,0 +1,11 @@
+---
+title: KM0201F: ERROR_IndexInVirtualKeySection
+---
+
+| | |
+|------------|---------- |
+| Message | 'index' command is illegal in virtual key section |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_IndexInVirtualKeySection` |
+
+
diff --git a/developer/docs/help/reference/messages/km02020.md b/developer/docs/help/reference/messages/km02020.md
new file mode 100644
index 00000000000..ab2c0c7177f
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02020.md
@@ -0,0 +1,11 @@
+---
+title: KM02020: ERROR_InvalidIndex
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'index' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidIndex` |
+
+
diff --git a/developer/docs/help/reference/messages/km02021.md b/developer/docs/help/reference/messages/km02021.md
new file mode 100644
index 00000000000..4082b601640
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02021.md
@@ -0,0 +1,11 @@
+---
+title: KM02021: ERROR_OutsInVirtualKeySection
+---
+
+| | |
+|------------|---------- |
+| Message | 'outs' command is illegal in virtual key section |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_OutsInVirtualKeySection` |
+
+
diff --git a/developer/docs/help/reference/messages/km02022.md b/developer/docs/help/reference/messages/km02022.md
new file mode 100644
index 00000000000..10887e79098
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02022.md
@@ -0,0 +1,11 @@
+---
+title: KM02022: ERROR_InvalidOuts
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'outs' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidOuts` |
+
+
diff --git a/developer/docs/help/reference/messages/km02024.md b/developer/docs/help/reference/messages/km02024.md
new file mode 100644
index 00000000000..bee175522c1
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02024.md
@@ -0,0 +1,11 @@
+---
+title: KM02024: ERROR_ContextInVirtualKeySection
+---
+
+| | |
+|------------|---------- |
+| Message | 'context' command is illegal in virtual key section |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_ContextInVirtualKeySection` |
+
+
diff --git a/developer/docs/help/reference/messages/km02025.md b/developer/docs/help/reference/messages/km02025.md
new file mode 100644
index 00000000000..74183960bcb
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02025.md
@@ -0,0 +1,11 @@
+---
+title: KM02025: ERROR_InvalidUse
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'use' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidUse` |
+
+
diff --git a/developer/docs/help/reference/messages/km02026.md b/developer/docs/help/reference/messages/km02026.md
new file mode 100644
index 00000000000..3f23dc452bd
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02026.md
@@ -0,0 +1,11 @@
+---
+title: KM02026: ERROR_GroupDoesNotExist
+---
+
+| | |
+|------------|---------- |
+| Message | Group does not exist |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_GroupDoesNotExist` |
+
+
diff --git a/developer/docs/help/reference/messages/km02027.md b/developer/docs/help/reference/messages/km02027.md
new file mode 100644
index 00000000000..62339e8f9c2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02027.md
@@ -0,0 +1,11 @@
+---
+title: KM02027: ERROR_VirtualKeyNotAllowedHere
+---
+
+| | |
+|------------|---------- |
+| Message | Virtual key is not allowed here |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_VirtualKeyNotAllowedHere` |
+
+
diff --git a/developer/docs/help/reference/messages/km02028.md b/developer/docs/help/reference/messages/km02028.md
new file mode 100644
index 00000000000..cf0861c63be
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02028.md
@@ -0,0 +1,11 @@
+---
+title: KM02028: ERROR_InvalidSwitch
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'switch' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidSwitch` |
+
+
diff --git a/developer/docs/help/reference/messages/km02029.md b/developer/docs/help/reference/messages/km02029.md
new file mode 100644
index 00000000000..4033972752b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02029.md
@@ -0,0 +1,11 @@
+---
+title: KM02029: ERROR_NoTokensFound
+---
+
+| | |
+|------------|---------- |
+| Message | No tokens found in line |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_NoTokensFound` |
+
+
diff --git a/developer/docs/help/reference/messages/km0202a.md b/developer/docs/help/reference/messages/km0202a.md
new file mode 100644
index 00000000000..163d7072039
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0202a.md
@@ -0,0 +1,11 @@
+---
+title: KM0202A: ERROR_InvalidLineContinuation
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid line continuation |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidLineContinuation` |
+
+
diff --git a/developer/docs/help/reference/messages/km0202b.md b/developer/docs/help/reference/messages/km0202b.md
new file mode 100644
index 00000000000..72457a2dc4f
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0202b.md
@@ -0,0 +1,11 @@
+---
+title: KM0202B: ERROR_LineTooLong
+---
+
+| | |
+|------------|---------- |
+| Message | Line too long |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_LineTooLong` |
+
+
diff --git a/developer/docs/help/reference/messages/km0202c.md b/developer/docs/help/reference/messages/km0202c.md
new file mode 100644
index 00000000000..9e5260a5eed
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0202c.md
@@ -0,0 +1,11 @@
+---
+title: KM0202C: ERROR_InvalidCopyright
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'copyright' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidCopyright` |
+
+
diff --git a/developer/docs/help/reference/messages/km0202d.md b/developer/docs/help/reference/messages/km0202d.md
new file mode 100644
index 00000000000..523b8123df4
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0202d.md
@@ -0,0 +1,11 @@
+---
+title: KM0202D: ERROR_CodeInvalidInThisSection
+---
+
+| | |
+|------------|---------- |
+| Message | This line is invalid in this section of the file |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CodeInvalidInThisSection` |
+
+
diff --git a/developer/docs/help/reference/messages/km0202e.md b/developer/docs/help/reference/messages/km0202e.md
new file mode 100644
index 00000000000..1d9acf621e0
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0202e.md
@@ -0,0 +1,11 @@
+---
+title: KM0202E: ERROR_InvalidMessage
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'message' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidMessage` |
+
+
diff --git a/developer/docs/help/reference/messages/km0202f.md b/developer/docs/help/reference/messages/km0202f.md
new file mode 100644
index 00000000000..606967be92f
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0202f.md
@@ -0,0 +1,11 @@
+---
+title: KM0202F: ERROR_InvalidLanguageName
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'languagename' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidLanguageName` |
+
+
diff --git a/developer/docs/help/reference/messages/km02030.md b/developer/docs/help/reference/messages/km02030.md
new file mode 100644
index 00000000000..5c912beafae
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02030.md
@@ -0,0 +1,11 @@
+---
+title: KM02030: ERROR_InvalidBitmapLine
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'bitmaps' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidBitmapLine` |
+
+
diff --git a/developer/docs/help/reference/messages/km02031.md b/developer/docs/help/reference/messages/km02031.md
new file mode 100644
index 00000000000..b73d1a59ea4
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02031.md
@@ -0,0 +1,11 @@
+---
+title: KM02031: ERROR_CannotReadBitmapFile
+---
+
+| | |
+|------------|---------- |
+| Message | Cannot open the bitmap or icon file for reading |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CannotReadBitmapFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km02032.md b/developer/docs/help/reference/messages/km02032.md
new file mode 100644
index 00000000000..a555afd4d6c
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02032.md
@@ -0,0 +1,11 @@
+---
+title: KM02032: ERROR_IndexDoesNotPointToAny
+---
+
+| | |
+|------------|---------- |
+| Message | An index\(\) in the output does not have a corresponding any\(\) statement |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_IndexDoesNotPointToAny` |
+
+
diff --git a/developer/docs/help/reference/messages/km02033.md b/developer/docs/help/reference/messages/km02033.md
new file mode 100644
index 00000000000..7980984fff2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02033.md
@@ -0,0 +1,11 @@
+---
+title: KM02033: ERROR_ReservedCharacter
+---
+
+| | |
+|------------|---------- |
+| Message | A reserved character was found |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_ReservedCharacter` |
+
+
diff --git a/developer/docs/help/reference/messages/km02034.md b/developer/docs/help/reference/messages/km02034.md
new file mode 100644
index 00000000000..d5a1a6f8390
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02034.md
@@ -0,0 +1,11 @@
+---
+title: KM02034: ERROR_InvalidCharacter
+---
+
+| | |
+|------------|---------- |
+| Message | A character was found that is outside the valid Unicode range \(U\+0000 \- U\+10FFFF\) |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidCharacter` |
+
+
diff --git a/developer/docs/help/reference/messages/km02035.md b/developer/docs/help/reference/messages/km02035.md
new file mode 100644
index 00000000000..e8141d6387e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02035.md
@@ -0,0 +1,11 @@
+---
+title: KM02035: ERROR_InvalidCall
+---
+
+| | |
+|------------|---------- |
+| Message | The 'call' command is invalid |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidCall` |
+
+
diff --git a/developer/docs/help/reference/messages/km02036.md b/developer/docs/help/reference/messages/km02036.md
new file mode 100644
index 00000000000..ab056bf0a27
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02036.md
@@ -0,0 +1,11 @@
+---
+title: KM02036: ERROR_CallInVirtualKeySection
+---
+
+| | |
+|------------|---------- |
+| Message | 'call' command is illegal in virtual key section |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CallInVirtualKeySection` |
+
+
diff --git a/developer/docs/help/reference/messages/km02037.md b/developer/docs/help/reference/messages/km02037.md
new file mode 100644
index 00000000000..9b15dc2d7f0
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02037.md
@@ -0,0 +1,11 @@
+---
+title: KM02037: ERROR_CodeInvalidInKeyStore
+---
+
+| | |
+|------------|---------- |
+| Message | The command is invalid inside a store that is used in a key part of the rule |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CodeInvalidInKeyStore` |
+
+
diff --git a/developer/docs/help/reference/messages/km02038.md b/developer/docs/help/reference/messages/km02038.md
new file mode 100644
index 00000000000..2b4e35050b3
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02038.md
@@ -0,0 +1,11 @@
+---
+title: KM02038: ERROR_CannotLoadIncludeFile
+---
+
+| | |
+|------------|---------- |
+| Message | Cannot load the included file: it is either invalid or does not exist |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CannotLoadIncludeFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km02039.md b/developer/docs/help/reference/messages/km02039.md
new file mode 100644
index 00000000000..5b9a6cbe0db
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02039.md
@@ -0,0 +1,11 @@
+---
+title: KM02039: ERROR_60FeatureOnly_EthnologueCode
+---
+
+| | |
+|------------|---------- |
+| Message | EthnologueCode system store requires VERSION 6\.0 or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_60FeatureOnly_EthnologueCode` |
+
+
diff --git a/developer/docs/help/reference/messages/km0203a.md b/developer/docs/help/reference/messages/km0203a.md
new file mode 100644
index 00000000000..02e84c70f92
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0203a.md
@@ -0,0 +1,11 @@
+---
+title: KM0203A: ERROR_60FeatureOnly_MnemonicLayout
+---
+
+| | |
+|------------|---------- |
+| Message | MnemonicLayout functionality requires VERSION 6\.0 or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_60FeatureOnly_MnemonicLayout` |
+
+
diff --git a/developer/docs/help/reference/messages/km0203b.md b/developer/docs/help/reference/messages/km0203b.md
new file mode 100644
index 00000000000..3dff8c79869
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0203b.md
@@ -0,0 +1,11 @@
+---
+title: KM0203B: ERROR_60FeatureOnly_OldCharPosMatching
+---
+
+| | |
+|------------|---------- |
+| Message | OldCharPosMatching system store requires VERSION 6\.0 or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_60FeatureOnly_OldCharPosMatching` |
+
+
diff --git a/developer/docs/help/reference/messages/km0203c.md b/developer/docs/help/reference/messages/km0203c.md
new file mode 100644
index 00000000000..270b19f3f9f
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0203c.md
@@ -0,0 +1,11 @@
+---
+title: KM0203C: ERROR_60FeatureOnly_NamedCodes
+---
+
+| | |
+|------------|---------- |
+| Message | Named character constants requires VERSION 6\.0 or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_60FeatureOnly_NamedCodes` |
+
+
diff --git a/developer/docs/help/reference/messages/km0203d.md b/developer/docs/help/reference/messages/km0203d.md
new file mode 100644
index 00000000000..bc67492e361
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0203d.md
@@ -0,0 +1,11 @@
+---
+title: KM0203D: ERROR_60FeatureOnly_Contextn
+---
+
+| | |
+|------------|---------- |
+| Message | Context\(n\) requires VERSION 6\.0 or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_60FeatureOnly_Contextn` |
+
+
diff --git a/developer/docs/help/reference/messages/km0203e.md b/developer/docs/help/reference/messages/km0203e.md
new file mode 100644
index 00000000000..4f4e91b205b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0203e.md
@@ -0,0 +1,11 @@
+---
+title: KM0203E: ERROR_501FeatureOnly_Call
+---
+
+| | |
+|------------|---------- |
+| Message | Call\(\) requires VERSION 5\.01 or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_501FeatureOnly_Call` |
+
+
diff --git a/developer/docs/help/reference/messages/km0203f.md b/developer/docs/help/reference/messages/km0203f.md
new file mode 100644
index 00000000000..c9348072966
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0203f.md
@@ -0,0 +1,11 @@
+---
+title: KM0203F: ERROR_InvalidNamedCode
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid named code constant |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidNamedCode` |
+
+
diff --git a/developer/docs/help/reference/messages/km02040.md b/developer/docs/help/reference/messages/km02040.md
new file mode 100644
index 00000000000..6d6884f63af
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02040.md
@@ -0,0 +1,11 @@
+---
+title: KM02040: ERROR_InvalidSystemStore
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid system store name found |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidSystemStore` |
+
+
diff --git a/developer/docs/help/reference/messages/km02044.md b/developer/docs/help/reference/messages/km02044.md
new file mode 100644
index 00000000000..9d64c33fb04
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02044.md
@@ -0,0 +1,11 @@
+---
+title: KM02044: ERROR_60FeatureOnly_VirtualCharKey
+---
+
+| | |
+|------------|---------- |
+| Message | Virtual character keys require VERSION 6\.0 or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_60FeatureOnly_VirtualCharKey` |
+
+
diff --git a/developer/docs/help/reference/messages/km02045.md b/developer/docs/help/reference/messages/km02045.md
new file mode 100644
index 00000000000..ff5f5707a6a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02045.md
@@ -0,0 +1,11 @@
+---
+title: KM02045: ERROR_VersionAlreadyIncluded
+---
+
+| | |
+|------------|---------- |
+| Message | Only one VERSION or store\(version\) line allowed in a source file\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_VersionAlreadyIncluded` |
+
+
diff --git a/developer/docs/help/reference/messages/km02046.md b/developer/docs/help/reference/messages/km02046.md
new file mode 100644
index 00000000000..2e7458348ae
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02046.md
@@ -0,0 +1,11 @@
+---
+title: KM02046: ERROR_70FeatureOnly
+---
+
+| | |
+|------------|---------- |
+| Message | This feature requires store\(version\) '7\.0' or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_70FeatureOnly` |
+
+
diff --git a/developer/docs/help/reference/messages/km02047.md b/developer/docs/help/reference/messages/km02047.md
new file mode 100644
index 00000000000..4c7d6446ef8
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02047.md
@@ -0,0 +1,11 @@
+---
+title: KM02047: ERROR_80FeatureOnly
+---
+
+| | |
+|------------|---------- |
+| Message | This feature requires store\(version\) '8\.0' or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_80FeatureOnly` |
+
+
diff --git a/developer/docs/help/reference/messages/km02048.md b/developer/docs/help/reference/messages/km02048.md
new file mode 100644
index 00000000000..e5f84c82e69
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02048.md
@@ -0,0 +1,11 @@
+---
+title: KM02048: ERROR_InvalidInVirtualKeySection
+---
+
+| | |
+|------------|---------- |
+| Message | This statement is not valid in a virtual key section |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidInVirtualKeySection` |
+
+
diff --git a/developer/docs/help/reference/messages/km02049.md b/developer/docs/help/reference/messages/km02049.md
new file mode 100644
index 00000000000..ead8d86ad6d
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02049.md
@@ -0,0 +1,11 @@
+---
+title: KM02049: ERROR_InvalidIf
+---
+
+| | |
+|------------|---------- |
+| Message | The if\(\) statement is not valid |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidIf` |
+
+
diff --git a/developer/docs/help/reference/messages/km0204a.md b/developer/docs/help/reference/messages/km0204a.md
new file mode 100644
index 00000000000..8b5878c7aa2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0204a.md
@@ -0,0 +1,11 @@
+---
+title: KM0204A: ERROR_InvalidReset
+---
+
+| | |
+|------------|---------- |
+| Message | The reset\(\) statement is not valid |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidReset` |
+
+
diff --git a/developer/docs/help/reference/messages/km0204b.md b/developer/docs/help/reference/messages/km0204b.md
new file mode 100644
index 00000000000..aa837630090
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0204b.md
@@ -0,0 +1,11 @@
+---
+title: KM0204B: ERROR_InvalidSet
+---
+
+| | |
+|------------|---------- |
+| Message | The set\(\) statement is not valid |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidSet` |
+
+
diff --git a/developer/docs/help/reference/messages/km0204c.md b/developer/docs/help/reference/messages/km0204c.md
new file mode 100644
index 00000000000..9cedcbd3250
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0204c.md
@@ -0,0 +1,11 @@
+---
+title: KM0204C: ERROR_InvalidSave
+---
+
+| | |
+|------------|---------- |
+| Message | The save\(\) statement is not valid |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidSave` |
+
+
diff --git a/developer/docs/help/reference/messages/km0204d.md b/developer/docs/help/reference/messages/km0204d.md
new file mode 100644
index 00000000000..a71ea826f65
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0204d.md
@@ -0,0 +1,11 @@
+---
+title: KM0204D: ERROR_InvalidEthnologueCode
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid ethnologuecode format |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidEthnologueCode` |
+
+
diff --git a/developer/docs/help/reference/messages/km0204e.md b/developer/docs/help/reference/messages/km0204e.md
new file mode 100644
index 00000000000..22c4c97da4e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0204e.md
@@ -0,0 +1,11 @@
+---
+title: KM0204E: FATAL_CannotCreateTempfile
+---
+
+| | |
+|------------|---------- |
+| Message | Cannot create temp file |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_CannotCreateTempfile` |
+
+
diff --git a/developer/docs/help/reference/messages/km0204f.md b/developer/docs/help/reference/messages/km0204f.md
new file mode 100644
index 00000000000..846eff8be96
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0204f.md
@@ -0,0 +1,11 @@
+---
+title: KM0204F: ERROR_90FeatureOnly_IfSystemStores
+---
+
+| | |
+|------------|---------- |
+| Message | if\(store\) requires store\(version\) '9\.0' or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_90FeatureOnly_IfSystemStores` |
+
+
diff --git a/developer/docs/help/reference/messages/km02050.md b/developer/docs/help/reference/messages/km02050.md
new file mode 100644
index 00000000000..facdd2272a2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02050.md
@@ -0,0 +1,11 @@
+---
+title: KM02050: ERROR_IfSystemStore_NotFound
+---
+
+| | |
+|------------|---------- |
+| Message | System store in if\(\) not found |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_IfSystemStore_NotFound` |
+
+
diff --git a/developer/docs/help/reference/messages/km02051.md b/developer/docs/help/reference/messages/km02051.md
new file mode 100644
index 00000000000..3eda67010db
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02051.md
@@ -0,0 +1,11 @@
+---
+title: KM02051: ERROR_90FeatureOnly_SetSystemStores
+---
+
+| | |
+|------------|---------- |
+| Message | set\(store\) requires store\(version\) '9\.0' or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_90FeatureOnly_SetSystemStores` |
+
+
diff --git a/developer/docs/help/reference/messages/km02052.md b/developer/docs/help/reference/messages/km02052.md
new file mode 100644
index 00000000000..caaa5e7235e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02052.md
@@ -0,0 +1,11 @@
+---
+title: KM02052: ERROR_SetSystemStore_NotFound
+---
+
+| | |
+|------------|---------- |
+| Message | System store in set\(\) not found |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_SetSystemStore_NotFound` |
+
+
diff --git a/developer/docs/help/reference/messages/km02053.md b/developer/docs/help/reference/messages/km02053.md
new file mode 100644
index 00000000000..1e2558d4c87
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02053.md
@@ -0,0 +1,11 @@
+---
+title: KM02053: ERROR_90FeatureOnlyVirtualKeyDictionary
+---
+
+| | |
+|------------|---------- |
+| Message | Custom virtual key names require store\(version\) '9\.0' |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_90FeatureOnlyVirtualKeyDictionary` |
+
+
diff --git a/developer/docs/help/reference/messages/km02054.md b/developer/docs/help/reference/messages/km02054.md
new file mode 100644
index 00000000000..c14ba1f9490
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02054.md
@@ -0,0 +1,11 @@
+---
+title: KM02054: ERROR_NotSupportedInKeymanWebContext
+---
+
+| | |
+|------------|---------- |
+| Message | Statement '<param>' is not currently supported in context for web and touch targets |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_NotSupportedInKeymanWebContext` |
+
+
diff --git a/developer/docs/help/reference/messages/km02055.md b/developer/docs/help/reference/messages/km02055.md
new file mode 100644
index 00000000000..081447004e3
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02055.md
@@ -0,0 +1,11 @@
+---
+title: KM02055: ERROR_NotSupportedInKeymanWebOutput
+---
+
+| | |
+|------------|---------- |
+| Message | Statement '<param>' is not currently supported in output for web and touch targets |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_NotSupportedInKeymanWebOutput` |
+
+
diff --git a/developer/docs/help/reference/messages/km02056.md b/developer/docs/help/reference/messages/km02056.md
new file mode 100644
index 00000000000..4e7a9e6e03b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02056.md
@@ -0,0 +1,11 @@
+---
+title: KM02056: ERROR_NotSupportedInKeymanWebStore
+---
+
+| | |
+|------------|---------- |
+| Message | '<param>' is not currently supported in store '<param>' when used by any or index for web and touch targets |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_NotSupportedInKeymanWebStore` |
+
+
diff --git a/developer/docs/help/reference/messages/km02057.md b/developer/docs/help/reference/messages/km02057.md
new file mode 100644
index 00000000000..b74ebea922a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02057.md
@@ -0,0 +1,11 @@
+---
+title: KM02057: ERROR_VirtualCharacterKeysNotSupportedInKeymanWeb
+---
+
+| | |
+|------------|---------- |
+| Message | Virtual character keys not currently supported in KeymanWeb |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_VirtualCharacterKeysNotSupportedInKeymanWeb` |
+
+
diff --git a/developer/docs/help/reference/messages/km02058.md b/developer/docs/help/reference/messages/km02058.md
new file mode 100644
index 00000000000..b6b63eae273
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02058.md
@@ -0,0 +1,11 @@
+---
+title: KM02058: ERROR_VirtualKeysNotValidForMnemonicLayouts
+---
+
+| | |
+|------------|---------- |
+| Message | Virtual keys are not valid for mnemonic layouts |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_VirtualKeysNotValidForMnemonicLayouts` |
+
+
diff --git a/developer/docs/help/reference/messages/km02059.md b/developer/docs/help/reference/messages/km02059.md
new file mode 100644
index 00000000000..a100e56abef
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02059.md
@@ -0,0 +1,11 @@
+---
+title: KM02059: ERROR_InvalidTouchLayoutFile
+---
+
+| | |
+|------------|---------- |
+| Message | Touch layout file <param> is not valid |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidTouchLayoutFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km0205a.md b/developer/docs/help/reference/messages/km0205a.md
new file mode 100644
index 00000000000..625b46b4b9a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0205a.md
@@ -0,0 +1,11 @@
+---
+title: KM0205A: ERROR_TouchLayoutInvalidIdentifier
+---
+
+| | |
+|------------|---------- |
+| Message | Key "<param>" on "<param>", layer "<param>" has an invalid identifier\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_TouchLayoutInvalidIdentifier` |
+
+
diff --git a/developer/docs/help/reference/messages/km0205b.md b/developer/docs/help/reference/messages/km0205b.md
new file mode 100644
index 00000000000..e09dc68039b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0205b.md
@@ -0,0 +1,11 @@
+---
+title: KM0205B: ERROR_InvalidKeyCode
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid key identifier "<param>" |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidKeyCode` |
+
+
diff --git a/developer/docs/help/reference/messages/km0205c.md b/developer/docs/help/reference/messages/km0205c.md
new file mode 100644
index 00000000000..563f7650e9d
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0205c.md
@@ -0,0 +1,11 @@
+---
+title: KM0205C: ERROR_90FeatureOnlyLayoutFile
+---
+
+| | |
+|------------|---------- |
+| Message | Touch layout file reference requires store\(version\) '9\.0'or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_90FeatureOnlyLayoutFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km0205d.md b/developer/docs/help/reference/messages/km0205d.md
new file mode 100644
index 00000000000..67620cfbc98
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0205d.md
@@ -0,0 +1,11 @@
+---
+title: KM0205D: ERROR_90FeatureOnlyKeyboardVersion
+---
+
+| | |
+|------------|---------- |
+| Message | KeyboardVersion system store requires store\(version\) '9\.0'or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_90FeatureOnlyKeyboardVersion` |
+
+
diff --git a/developer/docs/help/reference/messages/km0205e.md b/developer/docs/help/reference/messages/km0205e.md
new file mode 100644
index 00000000000..12316dac807
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0205e.md
@@ -0,0 +1,11 @@
+---
+title: KM0205E: ERROR_KeyboardVersionFormatInvalid
+---
+
+| | |
+|------------|---------- |
+| Message | KeyboardVersion format is invalid, expecting dot\-separated integers |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_KeyboardVersionFormatInvalid` |
+
+
diff --git a/developer/docs/help/reference/messages/km0205f.md b/developer/docs/help/reference/messages/km0205f.md
new file mode 100644
index 00000000000..0c79b8ba4b7
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0205f.md
@@ -0,0 +1,11 @@
+---
+title: KM0205F: ERROR_ContextExHasInvalidOffset
+---
+
+| | |
+|------------|---------- |
+| Message | context\(\) statement has offset out of range |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_ContextExHasInvalidOffset` |
+
+
diff --git a/developer/docs/help/reference/messages/km02060.md b/developer/docs/help/reference/messages/km02060.md
new file mode 100644
index 00000000000..c505c6d3f7c
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02060.md
@@ -0,0 +1,11 @@
+---
+title: KM02060: ERROR_90FeatureOnlyEmbedCSS
+---
+
+| | |
+|------------|---------- |
+| Message | Embedding CSS requires store\(version\) '9\.0'or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_90FeatureOnlyEmbedCSS` |
+
+
diff --git a/developer/docs/help/reference/messages/km02061.md b/developer/docs/help/reference/messages/km02061.md
new file mode 100644
index 00000000000..61240ea35fc
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02061.md
@@ -0,0 +1,11 @@
+---
+title: KM02061: ERROR_90FeatureOnlyTargets
+---
+
+| | |
+|------------|---------- |
+| Message | TARGETS system store requires store\(version\) '9\.0'or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_90FeatureOnlyTargets` |
+
+
diff --git a/developer/docs/help/reference/messages/km02062.md b/developer/docs/help/reference/messages/km02062.md
new file mode 100644
index 00000000000..6b6bc4b2cae
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02062.md
@@ -0,0 +1,11 @@
+---
+title: KM02062: ERROR_ContextAndIndexInvalidInMatchNomatch
+---
+
+| | |
+|------------|---------- |
+| Message | context and index statements cannot be used in a match or nomatch statement |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_ContextAndIndexInvalidInMatchNomatch` |
+
+
diff --git a/developer/docs/help/reference/messages/km02063.md b/developer/docs/help/reference/messages/km02063.md
new file mode 100644
index 00000000000..a947616a008
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02063.md
@@ -0,0 +1,11 @@
+---
+title: KM02063: ERROR_140FeatureOnlyContextAndNotAnyWeb
+---
+
+| | |
+|------------|---------- |
+| Message | For web and touch platforms, context\(\) statement referring to notany\(\) requires store\(version\) '14\.0'or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_140FeatureOnlyContextAndNotAnyWeb` |
+
+
diff --git a/developer/docs/help/reference/messages/km02064.md b/developer/docs/help/reference/messages/km02064.md
new file mode 100644
index 00000000000..84cf5a156af
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02064.md
@@ -0,0 +1,11 @@
+---
+title: KM02064: ERROR_ExpansionMustFollowCharacterOrVKey
+---
+
+| | |
+|------------|---------- |
+| Message | An expansion must follow a character or a virtual key |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_ExpansionMustFollowCharacterOrVKey` |
+
+
diff --git a/developer/docs/help/reference/messages/km02065.md b/developer/docs/help/reference/messages/km02065.md
new file mode 100644
index 00000000000..c8074db3a97
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02065.md
@@ -0,0 +1,11 @@
+---
+title: KM02065: ERROR_VKeyExpansionMustBeFollowedByVKey
+---
+
+| | |
+|------------|---------- |
+| Message | A virtual key expansion must be terminated by a virtual key |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_VKeyExpansionMustBeFollowedByVKey` |
+
+
diff --git a/developer/docs/help/reference/messages/km02066.md b/developer/docs/help/reference/messages/km02066.md
new file mode 100644
index 00000000000..874c3a62f5d
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02066.md
@@ -0,0 +1,11 @@
+---
+title: KM02066: ERROR_CharacterExpansionMustBeFollowedByCharacter
+---
+
+| | |
+|------------|---------- |
+| Message | A character expansion must be terminated by a character key |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CharacterExpansionMustBeFollowedByCharacter` |
+
+
diff --git a/developer/docs/help/reference/messages/km02067.md b/developer/docs/help/reference/messages/km02067.md
new file mode 100644
index 00000000000..aaacb3ad32d
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02067.md
@@ -0,0 +1,11 @@
+---
+title: KM02067: ERROR_VKeyExpansionMustUseConsistentShift
+---
+
+| | |
+|------------|---------- |
+| Message | A virtual key expansion must use the same shift state for both terminators |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_VKeyExpansionMustUseConsistentShift` |
+
+
diff --git a/developer/docs/help/reference/messages/km02068.md b/developer/docs/help/reference/messages/km02068.md
new file mode 100644
index 00000000000..88f83971b37
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02068.md
@@ -0,0 +1,11 @@
+---
+title: KM02068: ERROR_ExpansionMustBePositive
+---
+
+| | |
+|------------|---------- |
+| Message | An expansion must have positive difference \(i\.e\. A\-Z, not Z\-A\) |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_ExpansionMustBePositive` |
+
+
diff --git a/developer/docs/help/reference/messages/km02069.md b/developer/docs/help/reference/messages/km02069.md
new file mode 100644
index 00000000000..c7bdf32efd6
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02069.md
@@ -0,0 +1,11 @@
+---
+title: KM02069: ERROR_CasedKeysMustContainOnlyVirtualKeys
+---
+
+| | |
+|------------|---------- |
+| Message | The &CasedKeys system store must contain only virtual keys or characters found on a US English keyboard |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CasedKeysMustContainOnlyVirtualKeys` |
+
+
diff --git a/developer/docs/help/reference/messages/km0206a.md b/developer/docs/help/reference/messages/km0206a.md
new file mode 100644
index 00000000000..7a973006f2e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0206a.md
@@ -0,0 +1,11 @@
+---
+title: KM0206A: ERROR_CasedKeysMustNotIncludeShiftStates
+---
+
+| | |
+|------------|---------- |
+| Message | The &CasedKeys system store must not include shift states |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CasedKeysMustNotIncludeShiftStates` |
+
+
diff --git a/developer/docs/help/reference/messages/km0206b.md b/developer/docs/help/reference/messages/km0206b.md
new file mode 100644
index 00000000000..d774f7c263f
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0206b.md
@@ -0,0 +1,11 @@
+---
+title: KM0206B: ERROR_CasedKeysNotSupportedWithMnemonicLayout
+---
+
+| | |
+|------------|---------- |
+| Message | The &CasedKeys system store is not supported with mnemonic layouts |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CasedKeysNotSupportedWithMnemonicLayout` |
+
+
diff --git a/developer/docs/help/reference/messages/km0206c.md b/developer/docs/help/reference/messages/km0206c.md
new file mode 100644
index 00000000000..78bb1f3ab1b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0206c.md
@@ -0,0 +1,11 @@
+---
+title: KM0206C: ERROR_CannotUseReadWriteGroupFromReadonlyGroup
+---
+
+| | |
+|------------|---------- |
+| Message | Group used from a readonly group must also be readonly |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CannotUseReadWriteGroupFromReadonlyGroup` |
+
+
diff --git a/developer/docs/help/reference/messages/km0206d.md b/developer/docs/help/reference/messages/km0206d.md
new file mode 100644
index 00000000000..e97fa1f70dd
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0206d.md
@@ -0,0 +1,11 @@
+---
+title: KM0206D: ERROR_StatementNotPermittedInReadonlyGroup
+---
+
+| | |
+|------------|---------- |
+| Message | Statement is not permitted in output of readonly group |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_StatementNotPermittedInReadonlyGroup` |
+
+
diff --git a/developer/docs/help/reference/messages/km0206e.md b/developer/docs/help/reference/messages/km0206e.md
new file mode 100644
index 00000000000..d72ec373316
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0206e.md
@@ -0,0 +1,11 @@
+---
+title: KM0206E: ERROR_OutputInReadonlyGroup
+---
+
+| | |
+|------------|---------- |
+| Message | Output is not permitted in a readonly group |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_OutputInReadonlyGroup` |
+
+
diff --git a/developer/docs/help/reference/messages/km0206f.md b/developer/docs/help/reference/messages/km0206f.md
new file mode 100644
index 00000000000..304b460b550
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0206f.md
@@ -0,0 +1,11 @@
+---
+title: KM0206F: ERROR_NewContextGroupMustBeReadonly
+---
+
+| | |
+|------------|---------- |
+| Message | Group used in begin newContext must be readonly |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_NewContextGroupMustBeReadonly` |
+
+
diff --git a/developer/docs/help/reference/messages/km02070.md b/developer/docs/help/reference/messages/km02070.md
new file mode 100644
index 00000000000..fcf6cc352f4
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02070.md
@@ -0,0 +1,11 @@
+---
+title: KM02070: ERROR_PostKeystrokeGroupMustBeReadonly
+---
+
+| | |
+|------------|---------- |
+| Message | Group used in begin postKeystroke must be readonly |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_PostKeystrokeGroupMustBeReadonly` |
+
+
diff --git a/developer/docs/help/reference/messages/km02071.md b/developer/docs/help/reference/messages/km02071.md
new file mode 100644
index 00000000000..fcac6322233
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02071.md
@@ -0,0 +1,11 @@
+---
+title: KM02071: ERROR_DuplicateGroup
+---
+
+| | |
+|------------|---------- |
+| Message | A group with this name has already been defined\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_DuplicateGroup` |
+
+
diff --git a/developer/docs/help/reference/messages/km02072.md b/developer/docs/help/reference/messages/km02072.md
new file mode 100644
index 00000000000..8cfa13e5c33
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02072.md
@@ -0,0 +1,11 @@
+---
+title: KM02072: ERROR_DuplicateStore
+---
+
+| | |
+|------------|---------- |
+| Message | A store with this name has already been defined\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_DuplicateStore` |
+
+
diff --git a/developer/docs/help/reference/messages/km02073.md b/developer/docs/help/reference/messages/km02073.md
new file mode 100644
index 00000000000..0a0616d84b6
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02073.md
@@ -0,0 +1,11 @@
+---
+title: KM02073: ERROR_RepeatedBegin
+---
+
+| | |
+|------------|---------- |
+| Message | Begin has already been set |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_RepeatedBegin` |
+
+
diff --git a/developer/docs/help/reference/messages/km02074.md b/developer/docs/help/reference/messages/km02074.md
new file mode 100644
index 00000000000..a53219edc44
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02074.md
@@ -0,0 +1,11 @@
+---
+title: KM02074: ERROR_VirtualKeyInContext
+---
+
+| | |
+|------------|---------- |
+| Message | Virtual keys are not permitted in context |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_VirtualKeyInContext` |
+
+
diff --git a/developer/docs/help/reference/messages/km02075.md b/developer/docs/help/reference/messages/km02075.md
new file mode 100644
index 00000000000..8fbf31fbb5d
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02075.md
@@ -0,0 +1,11 @@
+---
+title: KM02075: ERROR_OutsTooLong
+---
+
+| | |
+|------------|---------- |
+| Message | Store cannot be inserted with outs\(\) as it makes the extended string too long |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_OutsTooLong` |
+
+
diff --git a/developer/docs/help/reference/messages/km02076.md b/developer/docs/help/reference/messages/km02076.md
new file mode 100644
index 00000000000..73a1129f326
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02076.md
@@ -0,0 +1,11 @@
+---
+title: KM02076: ERROR_ExtendedStringTooLong
+---
+
+| | |
+|------------|---------- |
+| Message | Extended string is too long |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_ExtendedStringTooLong` |
+
+
diff --git a/developer/docs/help/reference/messages/km02077.md b/developer/docs/help/reference/messages/km02077.md
new file mode 100644
index 00000000000..994bd6ba193
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02077.md
@@ -0,0 +1,11 @@
+---
+title: KM02077: ERROR_VirtualKeyExpansionTooLong
+---
+
+| | |
+|------------|---------- |
+| Message | Virtual key expansion is too large |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_VirtualKeyExpansionTooLong` |
+
+
diff --git a/developer/docs/help/reference/messages/km02078.md b/developer/docs/help/reference/messages/km02078.md
new file mode 100644
index 00000000000..1ad85f0dd97
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02078.md
@@ -0,0 +1,11 @@
+---
+title: KM02078: ERROR_CharacterRangeTooLong
+---
+
+| | |
+|------------|---------- |
+| Message | Character range is too large and cannot be expanded |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CharacterRangeTooLong` |
+
+
diff --git a/developer/docs/help/reference/messages/km02079.md b/developer/docs/help/reference/messages/km02079.md
new file mode 100644
index 00000000000..ccbb6aa607e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02079.md
@@ -0,0 +1,11 @@
+---
+title: KM02079: ERROR_NonBMPCharactersNotSupportedInKeySection
+---
+
+| | |
+|------------|---------- |
+| Message | Characters with codepoints over U\+FFFF are not supported in the key part of the rule |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_NonBMPCharactersNotSupportedInKeySection` |
+
+
diff --git a/developer/docs/help/reference/messages/km02080.md b/developer/docs/help/reference/messages/km02080.md
new file mode 100644
index 00000000000..189dc8a0572
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02080.md
@@ -0,0 +1,11 @@
+---
+title: KM02080: WARN_TooManyWarnings
+---
+
+| | |
+|------------|---------- |
+| Message | Too many warnings or errors |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_TooManyWarnings` |
+
+
diff --git a/developer/docs/help/reference/messages/km02081.md b/developer/docs/help/reference/messages/km02081.md
new file mode 100644
index 00000000000..28b67aef4c7
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02081.md
@@ -0,0 +1,11 @@
+---
+title: KM02081: WARN_OldVersion
+---
+
+| | |
+|------------|---------- |
+| Message | The keyboard file is an old version |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_OldVersion` |
+
+
diff --git a/developer/docs/help/reference/messages/km02082.md b/developer/docs/help/reference/messages/km02082.md
new file mode 100644
index 00000000000..5dd37cb366b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02082.md
@@ -0,0 +1,11 @@
+---
+title: KM02082: WARN_BitmapNotUsed
+---
+
+| | |
+|------------|---------- |
+| Message | The 'bitmaps' statement is obsolete and only the first bitmap referred to will be used, you should use 'bitmap'\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_BitmapNotUsed` |
+
+
diff --git a/developer/docs/help/reference/messages/km02083.md b/developer/docs/help/reference/messages/km02083.md
new file mode 100644
index 00000000000..06c5019eee3
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02083.md
@@ -0,0 +1,11 @@
+---
+title: KM02083: WARN_CustomLanguagesNotSupported
+---
+
+| | |
+|------------|---------- |
+| Message | Languages over 0x1FF, 0x1F are not supported correctly by Windows\. You should use no LANGUAGE line instead\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_CustomLanguagesNotSupported` |
+
+
diff --git a/developer/docs/help/reference/messages/km02084.md b/developer/docs/help/reference/messages/km02084.md
new file mode 100644
index 00000000000..1f82992d643
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02084.md
@@ -0,0 +1,11 @@
+---
+title: KM02084: WARN_KeyBadLength
+---
+
+| | |
+|------------|---------- |
+| Message | There are too many characters in the keystroke part of the rule\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_KeyBadLength` |
+
+
diff --git a/developer/docs/help/reference/messages/km02085.md b/developer/docs/help/reference/messages/km02085.md
new file mode 100644
index 00000000000..23363e783e5
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02085.md
@@ -0,0 +1,11 @@
+---
+title: KM02085: WARN_IndexStoreShort
+---
+
+| | |
+|------------|---------- |
+| Message | The store referenced in index\(\) is shorter than the store referenced in any\(\) |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_IndexStoreShort` |
+
+
diff --git a/developer/docs/help/reference/messages/km02086.md b/developer/docs/help/reference/messages/km02086.md
new file mode 100644
index 00000000000..db603b5087a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02086.md
@@ -0,0 +1,11 @@
+---
+title: KM02086: WARN_UnicodeInANSIGroup
+---
+
+| | |
+|------------|---------- |
+| Message | A Unicode character was found in an ANSI group |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_UnicodeInANSIGroup` |
+
+
diff --git a/developer/docs/help/reference/messages/km02087.md b/developer/docs/help/reference/messages/km02087.md
new file mode 100644
index 00000000000..f4cf3fa7366
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02087.md
@@ -0,0 +1,11 @@
+---
+title: KM02087: WARN_ANSIInUnicodeGroup
+---
+
+| | |
+|------------|---------- |
+| Message | An ANSI character was found in a Unicode group |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_ANSIInUnicodeGroup` |
+
+
diff --git a/developer/docs/help/reference/messages/km02088.md b/developer/docs/help/reference/messages/km02088.md
new file mode 100644
index 00000000000..ee038ef1334
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02088.md
@@ -0,0 +1,11 @@
+---
+title: KM02088: WARN_UnicodeSurrogateUsed
+---
+
+| | |
+|------------|---------- |
+| Message | A Unicode surrogate character was found\. You should use Unicode scalar values to represent values > U\+FFFF |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_UnicodeSurrogateUsed` |
+
+
diff --git a/developer/docs/help/reference/messages/km02089.md b/developer/docs/help/reference/messages/km02089.md
new file mode 100644
index 00000000000..73632221bbe
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02089.md
@@ -0,0 +1,11 @@
+---
+title: KM02089: WARN_ReservedCharacter
+---
+
+| | |
+|------------|---------- |
+| Message | A Unicode character was found that should not be used |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_ReservedCharacter` |
+
+
diff --git a/developer/docs/help/reference/messages/km0208a.md b/developer/docs/help/reference/messages/km0208a.md
new file mode 100644
index 00000000000..3b567389fa0
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0208a.md
@@ -0,0 +1,11 @@
+---
+title: KM0208A: INFO_Info
+---
+
+| | |
+|------------|---------- |
+| Message | Information |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `INFO_Info` |
+
+
diff --git a/developer/docs/help/reference/messages/km0208b.md b/developer/docs/help/reference/messages/km0208b.md
new file mode 100644
index 00000000000..568ae0aa39a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0208b.md
@@ -0,0 +1,11 @@
+---
+title: KM0208B: WARN_VirtualKeyWithMnemonicLayout
+---
+
+| | |
+|------------|---------- |
+| Message | Virtual key used instead of virtual character key with a mnemonic layout |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_VirtualKeyWithMnemonicLayout` |
+
+
diff --git a/developer/docs/help/reference/messages/km0208c.md b/developer/docs/help/reference/messages/km0208c.md
new file mode 100644
index 00000000000..52c93a7a4cf
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0208c.md
@@ -0,0 +1,11 @@
+---
+title: KM0208C: WARN_VirtualCharKeyWithPositionalLayout
+---
+
+| | |
+|------------|---------- |
+| Message | Virtual character key used with a positional layout instead of mnemonic layout |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_VirtualCharKeyWithPositionalLayout` |
+
+
diff --git a/developer/docs/help/reference/messages/km0208d.md b/developer/docs/help/reference/messages/km0208d.md
new file mode 100644
index 00000000000..18cd9c5e62f
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0208d.md
@@ -0,0 +1,11 @@
+---
+title: KM0208D: WARN_StoreAlreadyUsedAsOptionOrCall
+---
+
+| | |
+|------------|---------- |
+| Message | Store already used as an option or in a call statement and should not be used as a normal store |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_StoreAlreadyUsedAsOptionOrCall` |
+
+
diff --git a/developer/docs/help/reference/messages/km0208e.md b/developer/docs/help/reference/messages/km0208e.md
new file mode 100644
index 00000000000..61f6d48d9b3
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0208e.md
@@ -0,0 +1,11 @@
+---
+title: KM0208E: WARN_StoreAlreadyUsedAsStoreOrCall
+---
+
+| | |
+|------------|---------- |
+| Message | Store already used as a normal store or in a call statement and should not be used as an option |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_StoreAlreadyUsedAsStoreOrCall` |
+
+
diff --git a/developer/docs/help/reference/messages/km0208f.md b/developer/docs/help/reference/messages/km0208f.md
new file mode 100644
index 00000000000..ca2313c89c6
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0208f.md
@@ -0,0 +1,11 @@
+---
+title: KM0208F: WARN_StoreAlreadyUsedAsStoreOrOption
+---
+
+| | |
+|------------|---------- |
+| Message | Store already used as a normal store or as an option and should not be used in a call statement |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_StoreAlreadyUsedAsStoreOrOption` |
+
+
diff --git a/developer/docs/help/reference/messages/km02090.md b/developer/docs/help/reference/messages/km02090.md
new file mode 100644
index 00000000000..654a26a3dcf
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02090.md
@@ -0,0 +1,11 @@
+---
+title: KM02090: WARN_PunctuationInEthnologueCode
+---
+
+| | |
+|------------|---------- |
+| Message | Punctuation should not be used to separate Ethnologue codes; instead use spaces |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_PunctuationInEthnologueCode` |
+
+
diff --git a/developer/docs/help/reference/messages/km02091.md b/developer/docs/help/reference/messages/km02091.md
new file mode 100644
index 00000000000..45c7f67bca8
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02091.md
@@ -0,0 +1,11 @@
+---
+title: KM02091: WARN_TouchLayoutMissingLayer
+---
+
+| | |
+|------------|---------- |
+| Message | Key "<param>" on platform "<param>", layer "<param>", references a missing layer "<param>" |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_TouchLayoutMissingLayer` |
+
+
diff --git a/developer/docs/help/reference/messages/km02092.md b/developer/docs/help/reference/messages/km02092.md
new file mode 100644
index 00000000000..f81acf778a2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02092.md
@@ -0,0 +1,11 @@
+---
+title: KM02092: WARN_TouchLayoutCustomKeyNotDefined
+---
+
+| | |
+|------------|---------- |
+| Message | Key "<param>" on platform "<param>", layer "<param>", is a custom key but has no corresponding rule in the source\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_TouchLayoutCustomKeyNotDefined` |
+
+
diff --git a/developer/docs/help/reference/messages/km02093.md b/developer/docs/help/reference/messages/km02093.md
new file mode 100644
index 00000000000..344c56f654e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02093.md
@@ -0,0 +1,11 @@
+---
+title: KM02093: WARN_TouchLayoutMissingRequiredKeys
+---
+
+| | |
+|------------|---------- |
+| Message | Layer "<param>" on platform "<param>" is missing the required key\(s\) '<param>'\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_TouchLayoutMissingRequiredKeys` |
+
+
diff --git a/developer/docs/help/reference/messages/km02094.md b/developer/docs/help/reference/messages/km02094.md
new file mode 100644
index 00000000000..c445037d8c4
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02094.md
@@ -0,0 +1,11 @@
+---
+title: KM02094: WARN_HelpFileMissing
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> could not be loaded: |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_HelpFileMissing` |
+
+
diff --git a/developer/docs/help/reference/messages/km02095.md b/developer/docs/help/reference/messages/km02095.md
new file mode 100644
index 00000000000..f6250d30e80
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02095.md
@@ -0,0 +1,11 @@
+---
+title: KM02095: WARN_EmbedJsFileMissing
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> could not be loaded: |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_EmbedJsFileMissing` |
+
+
diff --git a/developer/docs/help/reference/messages/km02098.md b/developer/docs/help/reference/messages/km02098.md
new file mode 100644
index 00000000000..8ccdbd27284
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02098.md
@@ -0,0 +1,11 @@
+---
+title: KM02098: WARN_ExtendedShiftFlagsNotSupportedInKeymanWeb
+---
+
+| | |
+|------------|---------- |
+| Message | Extended shift flags <param> are not supported in KeymanWeb |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_ExtendedShiftFlagsNotSupportedInKeymanWeb` |
+
+
diff --git a/developer/docs/help/reference/messages/km02099.md b/developer/docs/help/reference/messages/km02099.md
new file mode 100644
index 00000000000..17a93b9f65e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02099.md
@@ -0,0 +1,11 @@
+---
+title: KM02099: WARN_TouchLayoutUnidentifiedKey
+---
+
+| | |
+|------------|---------- |
+| Message | A key on layer "<param>" has no identifier\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_TouchLayoutUnidentifiedKey` |
+
+
diff --git a/developer/docs/help/reference/messages/km0209a.md b/developer/docs/help/reference/messages/km0209a.md
new file mode 100644
index 00000000000..e49c8800bb3
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0209a.md
@@ -0,0 +1,11 @@
+---
+title: KM0209A: HINT_UnreachableKeyCode
+---
+
+| | |
+|------------|---------- |
+| Message | The rule will never be matched for key <param> because its key code is never fired\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `HINT_UnreachableKeyCode` |
+
+
diff --git a/developer/docs/help/reference/messages/km0209c.md b/developer/docs/help/reference/messages/km0209c.md
new file mode 100644
index 00000000000..0dd2d6ffb05
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0209c.md
@@ -0,0 +1,11 @@
+---
+title: KM0209C: WARN_PlatformNotInTargets
+---
+
+| | |
+|------------|---------- |
+| Message | The specified platform is not a target platform |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_PlatformNotInTargets` |
+
+
diff --git a/developer/docs/help/reference/messages/km0209d.md b/developer/docs/help/reference/messages/km0209d.md
new file mode 100644
index 00000000000..0ff7f4377a2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0209d.md
@@ -0,0 +1,11 @@
+---
+title: KM0209D: WARN_HeaderStatementIsDeprecated
+---
+
+| | |
+|------------|---------- |
+| Message | Header statements are deprecated; use instead the equivalent system store |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_HeaderStatementIsDeprecated` |
+
+
diff --git a/developer/docs/help/reference/messages/km0209e.md b/developer/docs/help/reference/messages/km0209e.md
new file mode 100644
index 00000000000..b3d21524d3a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0209e.md
@@ -0,0 +1,11 @@
+---
+title: KM0209E: WARN_UseNotLastStatementInRule
+---
+
+| | |
+|------------|---------- |
+| Message | A rule with use\(\) statements in the output should not have other content following the use\(\) statements |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_UseNotLastStatementInRule` |
+
+
diff --git a/developer/docs/help/reference/messages/km0209f.md b/developer/docs/help/reference/messages/km0209f.md
new file mode 100644
index 00000000000..b28554e71c6
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0209f.md
@@ -0,0 +1,11 @@
+---
+title: KM0209F: WARN_TouchLayoutFontShouldBeSameForAllPlatforms
+---
+
+| | |
+|------------|---------- |
+| Message | The touch layout font should be the same for all platforms\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_TouchLayoutFontShouldBeSameForAllPlatforms` |
+
+
diff --git a/developer/docs/help/reference/messages/km020a2.md b/developer/docs/help/reference/messages/km020a2.md
new file mode 100644
index 00000000000..e4e0530b22f
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020a2.md
@@ -0,0 +1,11 @@
+---
+title: KM020A2: WARN_KVKFileIsInSourceFormat
+---
+
+| | |
+|------------|---------- |
+| Message | \.kvk file should be binary but is an XML file |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_KVKFileIsInSourceFormat` |
+
+
diff --git a/developer/docs/help/reference/messages/km020a3.md b/developer/docs/help/reference/messages/km020a3.md
new file mode 100644
index 00000000000..36e2820f4ae
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020a3.md
@@ -0,0 +1,11 @@
+---
+title: KM020A3: WARN_DontMixChiralAndNonChiralModifiers
+---
+
+| | |
+|------------|---------- |
+| Message | This keyboard contains Ctrl,Alt and LCtrl,LAlt,RCtrl,RAlt sets of modifiers\. Use only one or the other set for web target\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_DontMixChiralAndNonChiralModifiers` |
+
+
diff --git a/developer/docs/help/reference/messages/km020a4.md b/developer/docs/help/reference/messages/km020a4.md
new file mode 100644
index 00000000000..e9f0cddc180
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020a4.md
@@ -0,0 +1,11 @@
+---
+title: KM020A4: WARN_MixingLeftAndRightModifiers
+---
+
+| | |
+|------------|---------- |
+| Message | Left and right modifiers should not both be used in the same rule |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_MixingLeftAndRightModifiers` |
+
+
diff --git a/developer/docs/help/reference/messages/km020a5.md b/developer/docs/help/reference/messages/km020a5.md
new file mode 100644
index 00000000000..3cafe009836
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020a5.md
@@ -0,0 +1,11 @@
+---
+title: KM020A5: WARN_LanguageHeadersDeprecatedInKeyman10
+---
+
+| | |
+|------------|---------- |
+| Message | This language header has been deprecated in Keyman 10\. Instead, add language metadata in the package file |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_LanguageHeadersDeprecatedInKeyman10` |
+
+
diff --git a/developer/docs/help/reference/messages/km020a6.md b/developer/docs/help/reference/messages/km020a6.md
new file mode 100644
index 00000000000..30962d42fe8
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020a6.md
@@ -0,0 +1,11 @@
+---
+title: KM020A6: HINT_NonUnicodeFile
+---
+
+| | |
+|------------|---------- |
+| Message | Keyman Developer has detected that the file has ANSI encoding\. Consider converting this file to UTF\-8 |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `HINT_NonUnicodeFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km020a8.md b/developer/docs/help/reference/messages/km020a8.md
new file mode 100644
index 00000000000..1a92a9c17fc
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020a8.md
@@ -0,0 +1,11 @@
+---
+title: KM020A8: WARN_HotkeyHasInvalidModifier
+---
+
+| | |
+|------------|---------- |
+| Message | Hotkey has modifiers that are not supported\. Use only SHIFT, CTRL and ALT |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_HotkeyHasInvalidModifier` |
+
+
diff --git a/developer/docs/help/reference/messages/km020a9.md b/developer/docs/help/reference/messages/km020a9.md
new file mode 100644
index 00000000000..906171ba8a4
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020a9.md
@@ -0,0 +1,11 @@
+---
+title: KM020A9: WARN_TouchLayoutSpecialLabelOnNormalKey
+---
+
+| | |
+|------------|---------- |
+| Message | Key "<param>" on platform "<param>", layer "<param>" does not have the key type "Special" or "Special \(active\)" but has the label "<param>"\. This feature is only supported in Keyman 14 or later |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_TouchLayoutSpecialLabelOnNormalKey` |
+
+
diff --git a/developer/docs/help/reference/messages/km020aa.md b/developer/docs/help/reference/messages/km020aa.md
new file mode 100644
index 00000000000..19505b3984a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020aa.md
@@ -0,0 +1,11 @@
+---
+title: KM020AA: WARN_OptionStoreNameInvalid
+---
+
+| | |
+|------------|---------- |
+| Message | The option store <param> should be named with characters in the range A\-Z, a\-z, 0\-9 and \_ only\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_OptionStoreNameInvalid` |
+
+
diff --git a/developer/docs/help/reference/messages/km020ab.md b/developer/docs/help/reference/messages/km020ab.md
new file mode 100644
index 00000000000..39a050f9995
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020ab.md
@@ -0,0 +1,11 @@
+---
+title: KM020AB: WARN_NulNotFirstStatementInContext
+---
+
+| | |
+|------------|---------- |
+| Message | nul must be the first statement in the context |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_NulNotFirstStatementInContext` |
+
+
diff --git a/developer/docs/help/reference/messages/km020ac.md b/developer/docs/help/reference/messages/km020ac.md
new file mode 100644
index 00000000000..87698b91387
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020ac.md
@@ -0,0 +1,11 @@
+---
+title: KM020AC: WARN_IfShouldBeAtStartOfContext
+---
+
+| | |
+|------------|---------- |
+| Message | if, platform and baselayout should be at start of context \(after nul, if present\) |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_IfShouldBeAtStartOfContext` |
+
+
diff --git a/developer/docs/help/reference/messages/km020ad.md b/developer/docs/help/reference/messages/km020ad.md
new file mode 100644
index 00000000000..0a5258da8f7
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020ad.md
@@ -0,0 +1,11 @@
+---
+title: KM020AD: WARN_KeyShouldIncludeNCaps
+---
+
+| | |
+|------------|---------- |
+| Message | Other rules which reference this key include CAPS or NCAPS modifiers, so this rule must include NCAPS modifier to avoid inconsistent matches |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_KeyShouldIncludeNCaps` |
+
+
diff --git a/developer/docs/help/reference/messages/km020ae.md b/developer/docs/help/reference/messages/km020ae.md
new file mode 100644
index 00000000000..15157c274b2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020ae.md
@@ -0,0 +1,11 @@
+---
+title: KM020AE: HINT_UnreachableRule
+---
+
+| | |
+|------------|---------- |
+| Message | This rule will never be matched as another rule takes precedence |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `HINT_UnreachableRule` |
+
+
diff --git a/developer/docs/help/reference/messages/km020af.md b/developer/docs/help/reference/messages/km020af.md
new file mode 100644
index 00000000000..b28458ca7d2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020af.md
@@ -0,0 +1,11 @@
+---
+title: KM020AF: WARN_VirtualKeyInOutput
+---
+
+| | |
+|------------|---------- |
+| Message | Virtual keys are not supported in output |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_VirtualKeyInOutput` |
+
+
diff --git a/developer/docs/help/reference/messages/km020c0.md b/developer/docs/help/reference/messages/km020c0.md
new file mode 100644
index 00000000000..73e90bd3dd2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020c0.md
@@ -0,0 +1,11 @@
+---
+title: KM020C0: FATAL_BufferOverflow
+---
+
+| | |
+|------------|---------- |
+| Message | The compiler memory buffer overflowed |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_BufferOverflow` |
+
+
diff --git a/developer/docs/help/reference/messages/km020c1.md b/developer/docs/help/reference/messages/km020c1.md
new file mode 100644
index 00000000000..7e82a1531f6
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020c1.md
@@ -0,0 +1,11 @@
+---
+title: KM020C1: FATAL_Break
+---
+
+| | |
+|------------|---------- |
+| Message | Compiler interrupted by user |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_Break` |
+
+
diff --git a/developer/docs/help/reference/messages/km02900.md b/developer/docs/help/reference/messages/km02900.md
new file mode 100644
index 00000000000..9b4cefdfa9a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02900.md
@@ -0,0 +1,13 @@
+---
+title: KM02900: FATAL_UnexpectedException
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_UnexpectedException` |
+
+Raised when KmnCompiler or one of its components has an internal
+error. If you experience this error, it should be reported to the Keyman
+team for resolution via https://github.com/keymanapp/keyman/issues/new.
diff --git a/developer/docs/help/reference/messages/km02901.md b/developer/docs/help/reference/messages/km02901.md
new file mode 100644
index 00000000000..1e9098c24e8
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02901.md
@@ -0,0 +1,13 @@
+---
+title: KM02901: FATAL_MissingWasmModule
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_MissingWasmModule` |
+
+Raised when the kmcmplib component could not be instantiated. This may indicate
+a configuration or dependency issue. Make sure you are running a Javascript
+engine that supports WASM, and that use of WASM is enabled.
diff --git a/developer/docs/help/reference/messages/km02903.md b/developer/docs/help/reference/messages/km02903.md
new file mode 100644
index 00000000000..df0c0087e01
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02903.md
@@ -0,0 +1,13 @@
+---
+title: KM02903: FATAL_CallbacksNotSet
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_CallbacksNotSet` |
+
+Raised when KmnCompiler or one of its components experiences an internal
+error. If you experience this error, it should be reported to the Keyman
+team for resolution via https://github.com/keymanapp/keyman/issues/new.
diff --git a/developer/docs/help/reference/messages/km02904.md b/developer/docs/help/reference/messages/km02904.md
new file mode 100644
index 00000000000..9661ae59203
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02904.md
@@ -0,0 +1,13 @@
+---
+title: KM02904: FATAL_UnicodeSetOutOfRange
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_UnicodeSetOutOfRange` |
+
+Raised when caller to UnicodeSet functions provides an invalid buffer. If
+you experience this error, it should be reported to the Keyman team for
+resolution via https://github.com/keymanapp/keyman/issues/new.
diff --git a/developer/docs/help/reference/messages/km02905.md b/developer/docs/help/reference/messages/km02905.md
new file mode 100644
index 00000000000..5b6982f18bb
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02905.md
@@ -0,0 +1,16 @@
+---
+title: KM02905: ERROR_UnicodeSetHasStrings
+---
+
+| | |
+|------------|---------- |
+| Message | uset contains strings, not allowed |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_UnicodeSetHasStrings` |
+
+The provided uset uses multi-character strings, (`{}` notation, e.g.
+`[żġħ{ie}{għ}]`. ). Although full UnicodeSets support strings, LDML
+keyboards do not support multi-character strings in usets. To resolve this,
+reformat the uset to avoid the use of multi-character strings.
+
+More on uset: https://www.unicode.org/reports/tr35/tr35-keyboards.html#element-uset
diff --git a/developer/docs/help/reference/messages/km02906.md b/developer/docs/help/reference/messages/km02906.md
new file mode 100644
index 00000000000..99dd1569569
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02906.md
@@ -0,0 +1,16 @@
+---
+title: KM02906: ERROR_UnicodeSetHasProperties
+---
+
+| | |
+|------------|---------- |
+| Message | uset contains properties, not allowed |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_UnicodeSetHasProperties` |
+
+The provided uset uses property notation (`\p{…}` or `[:…:]`). LDML
+keyboards do not support Unicode properties in usets, because that would
+make implementations dependent on a particular version of Unicode. To
+resolve this, reformat the uset to avoid the use of properties.
+
+More on uset: https://www.unicode.org/reports/tr35/tr35-keyboards.html#element-uset
diff --git a/developer/docs/help/reference/messages/km02907.md b/developer/docs/help/reference/messages/km02907.md
new file mode 100644
index 00000000000..deae301f509
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02907.md
@@ -0,0 +1,14 @@
+---
+title: KM02907: ERROR_UnicodeSetSyntaxError
+---
+
+| | |
+|------------|---------- |
+| Message | uset had a Syntax Error while parsing |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_UnicodeSetSyntaxError` |
+
+The provided uset has a syntax error and could not be parsed. Verify the
+format of the uset against the specification.
+
+More on uset: https://www.unicode.org/reports/tr35/tr35-keyboards.html#element-uset
diff --git a/developer/docs/help/reference/messages/km02908.md b/developer/docs/help/reference/messages/km02908.md
new file mode 100644
index 00000000000..d4c4ac5dc17
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02908.md
@@ -0,0 +1,15 @@
+---
+title: KM02908: ERROR_InvalidKvksFile
+---
+
+| | |
+|------------|---------- |
+| Message | Error encountered parsing <param>: unknown error |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidKvksFile` |
+
+The .kvks file could not be parsed because it was not a valid XML file.
+There may be additional information in the error message to help you
+resolve the error.
+
+More on .kvks file format: https://help.keyman.com/developer/current-version/reference/file-types/kvks
diff --git a/developer/docs/help/reference/messages/km02909.md b/developer/docs/help/reference/messages/km02909.md
new file mode 100644
index 00000000000..96d690960d9
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02909.md
@@ -0,0 +1,14 @@
+---
+title: KM02909: WARN_InvalidVkeyInKvksFile
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid virtual key <param> found in <param> |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_InvalidVkeyInKvksFile` |
+
+The .kvks file contained a virtual key that was not supported by
+Keyman. Remove this virtual key from the .kvks file.
+
+Supported virtual keys: https://help.keyman.com/developer/language/guide/virtual-keys#common-virtual-key-codes
diff --git a/developer/docs/help/reference/messages/km0290a.md b/developer/docs/help/reference/messages/km0290a.md
new file mode 100644
index 00000000000..fb5db9ed09e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0290a.md
@@ -0,0 +1,15 @@
+---
+title: KM0290A: ERROR_InvalidDisplayMapFile
+---
+
+| | |
+|------------|---------- |
+| Message | Error encountered parsing display map <param>: unknown error |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidDisplayMapFile` |
+
+The displayMap file could not be parsed because it was not a valid JSON
+file. There may be additional information in the error message to help you
+resolve the error.
+
+More on displayMap: https://help.keyman.com/developer/language/reference/displaymap
diff --git a/developer/docs/help/reference/messages/km0290b.md b/developer/docs/help/reference/messages/km0290b.md
new file mode 100644
index 00000000000..4fdb0f7d921
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0290b.md
@@ -0,0 +1,15 @@
+---
+title: KM0290B: ERROR_InvalidKvkFile
+---
+
+| | |
+|------------|---------- |
+| Message | Error encountered loading <param>: unknown error |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidKvkFile` |
+
+The .kvk file could not be loaded because it was not a valid format. There
+may be additional information in the error message to help you resolve the
+error.
+
+More on .kvk files: https://help.keyman.com/developer/current-version/reference/file-types/kvk
diff --git a/developer/docs/help/reference/messages/km0290c.md b/developer/docs/help/reference/messages/km0290c.md
new file mode 100644
index 00000000000..97d584ca979
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0290c.md
@@ -0,0 +1,12 @@
+---
+title: KM0290C: ERROR_FileNotFound
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> was not found |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_FileNotFound` |
+
+The file was not found on the disk. Verify that you have the correct path
+to the file.
diff --git a/developer/docs/help/reference/messages/km03001.md b/developer/docs/help/reference/messages/km03001.md
new file mode 100644
index 00000000000..b988ceb558c
--- /dev/null
+++ b/developer/docs/help/reference/messages/km03001.md
@@ -0,0 +1,11 @@
+---
+title: KM03001: FATAL_UnexpectedException
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages) |
+| Identifier | `FATAL_UnexpectedException` |
+
+
diff --git a/developer/docs/help/reference/messages/km03002.md b/developer/docs/help/reference/messages/km03002.md
new file mode 100644
index 00000000000..0066c30bb1a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km03002.md
@@ -0,0 +1,11 @@
+---
+title: KM03002: HINT_MixedNormalizationForms
+---
+
+| | |
+|------------|---------- |
+| Message | “<param>” is not in Unicode NFC\. Automatically converting to NFC\. |
+| Module | [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages) |
+| Identifier | `HINT_MixedNormalizationForms` |
+
+
diff --git a/developer/docs/help/reference/messages/km03003.md b/developer/docs/help/reference/messages/km03003.md
new file mode 100644
index 00000000000..d737092f030
--- /dev/null
+++ b/developer/docs/help/reference/messages/km03003.md
@@ -0,0 +1,11 @@
+---
+title: KM03003: HINT_DuplicateWordInSameFile
+---
+
+| | |
+|------------|---------- |
+| Message | duplicate word “<param>” found in same file; summing counts |
+| Module | [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages) |
+| Identifier | `HINT_DuplicateWordInSameFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km03004.md b/developer/docs/help/reference/messages/km03004.md
new file mode 100644
index 00000000000..5b468c561b5
--- /dev/null
+++ b/developer/docs/help/reference/messages/km03004.md
@@ -0,0 +1,11 @@
+---
+title: KM03004: ERROR_UnimplementedModelFormat
+---
+
+| | |
+|------------|---------- |
+| Message | Unimplemented model format: <param> |
+| Module | [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages) |
+| Identifier | `ERROR_UnimplementedModelFormat` |
+
+
diff --git a/developer/docs/help/reference/messages/km03005.md b/developer/docs/help/reference/messages/km03005.md
new file mode 100644
index 00000000000..fc972eb9e93
--- /dev/null
+++ b/developer/docs/help/reference/messages/km03005.md
@@ -0,0 +1,11 @@
+---
+title: KM03005: ERROR_UnknownModelFormat
+---
+
+| | |
+|------------|---------- |
+| Message | Unimplemented model format: <param> |
+| Module | [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages) |
+| Identifier | `ERROR_UnknownModelFormat` |
+
+
diff --git a/developer/docs/help/reference/messages/km03006.md b/developer/docs/help/reference/messages/km03006.md
new file mode 100644
index 00000000000..24d6bc43513
--- /dev/null
+++ b/developer/docs/help/reference/messages/km03006.md
@@ -0,0 +1,11 @@
+---
+title: KM03006: ERROR_NoDefaultExport
+---
+
+| | |
+|------------|---------- |
+| Message | Model source does have a default export\. Did you remember to write \`export default source;\`? |
+| Module | [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages) |
+| Identifier | `ERROR_NoDefaultExport` |
+
+
diff --git a/developer/docs/help/reference/messages/km03007.md b/developer/docs/help/reference/messages/km03007.md
new file mode 100644
index 00000000000..988d8f60658
--- /dev/null
+++ b/developer/docs/help/reference/messages/km03007.md
@@ -0,0 +1,11 @@
+---
+title: KM03007: ERROR_SearchTermToKeyMustBeExplicitlySpecified
+---
+
+| | |
+|------------|---------- |
+| Message | searchTermToKey must be explicitly specified |
+| Module | [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages) |
+| Identifier | `ERROR_SearchTermToKeyMustBeExplicitlySpecified` |
+
+
diff --git a/developer/docs/help/reference/messages/km03008.md b/developer/docs/help/reference/messages/km03008.md
new file mode 100644
index 00000000000..761fede21a3
--- /dev/null
+++ b/developer/docs/help/reference/messages/km03008.md
@@ -0,0 +1,11 @@
+---
+title: KM03008: ERROR_UTF16BEUnsupported
+---
+
+| | |
+|------------|---------- |
+| Message | UTF\-16BE is unsupported |
+| Module | [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages) |
+| Identifier | `ERROR_UTF16BEUnsupported` |
+
+
diff --git a/developer/docs/help/reference/messages/km03009.md b/developer/docs/help/reference/messages/km03009.md
new file mode 100644
index 00000000000..293f02e7920
--- /dev/null
+++ b/developer/docs/help/reference/messages/km03009.md
@@ -0,0 +1,11 @@
+---
+title: KM03009: ERROR_UnknownWordBreaker
+---
+
+| | |
+|------------|---------- |
+| Message | Unknown word breaker: <param> |
+| Module | [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages) |
+| Identifier | `ERROR_UnknownWordBreaker` |
+
+
diff --git a/developer/docs/help/reference/messages/km0300a.md b/developer/docs/help/reference/messages/km0300a.md
new file mode 100644
index 00000000000..db2e3070c0b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0300a.md
@@ -0,0 +1,11 @@
+---
+title: KM0300A: ERROR_UnsupportedScriptOverride
+---
+
+| | |
+|------------|---------- |
+| Message | Unsupported script override: <param> |
+| Module | [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages) |
+| Identifier | `ERROR_UnsupportedScriptOverride` |
+
+
diff --git a/developer/docs/help/reference/messages/km04001.md b/developer/docs/help/reference/messages/km04001.md
new file mode 100644
index 00000000000..c0d482c2c4e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04001.md
@@ -0,0 +1,11 @@
+---
+title: KM04001: FATAL_UnexpectedException
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `FATAL_UnexpectedException` |
+
+
diff --git a/developer/docs/help/reference/messages/km04002.md b/developer/docs/help/reference/messages/km04002.md
new file mode 100644
index 00000000000..d659496d1ac
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04002.md
@@ -0,0 +1,11 @@
+---
+title: KM04002: WARN_AbsolutePath
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> has an absolute path, which is not portable\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `WARN_AbsolutePath` |
+
+
diff --git a/developer/docs/help/reference/messages/km04003.md b/developer/docs/help/reference/messages/km04003.md
new file mode 100644
index 00000000000..209a2d37a31
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04003.md
@@ -0,0 +1,11 @@
+---
+title: KM04003: ERROR_FileDoesNotExist
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> does not exist\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_FileDoesNotExist` |
+
+
diff --git a/developer/docs/help/reference/messages/km04004.md b/developer/docs/help/reference/messages/km04004.md
new file mode 100644
index 00000000000..d5b104ddfce
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04004.md
@@ -0,0 +1,11 @@
+---
+title: KM04004: ERROR_FileCouldNotBeRead
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> could not be read: unknown error\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_FileCouldNotBeRead` |
+
+
diff --git a/developer/docs/help/reference/messages/km04005.md b/developer/docs/help/reference/messages/km04005.md
new file mode 100644
index 00000000000..494c040d142
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04005.md
@@ -0,0 +1,11 @@
+---
+title: KM04005: WARN_FileIsNotABinaryKvkFile
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> does not appear to be a valid binary \.kvk file; this may be an old package that includes an xml\-format \.kvk file\. You must update the package to include the compiled \.kvk file in the package\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `WARN_FileIsNotABinaryKvkFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km04006.md b/developer/docs/help/reference/messages/km04006.md
new file mode 100644
index 00000000000..696815d28fc
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04006.md
@@ -0,0 +1,11 @@
+---
+title: KM04006: ERROR_FollowKeyboardVersionNotAllowedForModelPackages
+---
+
+| | |
+|------------|---------- |
+| Message | FollowKeyboardVersion is not allowed in model packages |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_FollowKeyboardVersionNotAllowedForModelPackages` |
+
+
diff --git a/developer/docs/help/reference/messages/km04007.md b/developer/docs/help/reference/messages/km04007.md
new file mode 100644
index 00000000000..a9709d876c2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04007.md
@@ -0,0 +1,11 @@
+---
+title: KM04007: ERROR_FollowKeyboardVersionButNoKeyboards
+---
+
+| | |
+|------------|---------- |
+| Message | FollowKeyboardVersion is set, but the package contains no keyboards |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_FollowKeyboardVersionButNoKeyboards` |
+
+
diff --git a/developer/docs/help/reference/messages/km04008.md b/developer/docs/help/reference/messages/km04008.md
new file mode 100644
index 00000000000..301b6ea85f7
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04008.md
@@ -0,0 +1,11 @@
+---
+title: KM04008: ERROR_KeyboardContentFileNotFound
+---
+
+| | |
+|------------|---------- |
+| Message | Keyboard <param> was listed in <Keyboards> but a corresponding \.kmx file was not found in <Files> |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_KeyboardContentFileNotFound` |
+
+
diff --git a/developer/docs/help/reference/messages/km04009.md b/developer/docs/help/reference/messages/km04009.md
new file mode 100644
index 00000000000..5e2ebee13e5
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04009.md
@@ -0,0 +1,11 @@
+---
+title: KM04009: ERROR_KeyboardFileNotValid
+---
+
+| | |
+|------------|---------- |
+| Message | Keyboard file <param> is not a valid \.kmx file: unknown error |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_KeyboardFileNotValid` |
+
+
diff --git a/developer/docs/help/reference/messages/km0400a.md b/developer/docs/help/reference/messages/km0400a.md
new file mode 100644
index 00000000000..f32c195b5d9
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0400a.md
@@ -0,0 +1,11 @@
+---
+title: KM0400A: INFO_KeyboardFileHasNoKeyboardVersion
+---
+
+| | |
+|------------|---------- |
+| Message | Keyboard file <param> has no &KeyboardVersion store, using default '0\.0' |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `INFO_KeyboardFileHasNoKeyboardVersion` |
+
+
diff --git a/developer/docs/help/reference/messages/km0400b.md b/developer/docs/help/reference/messages/km0400b.md
new file mode 100644
index 00000000000..04c3af283b4
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0400b.md
@@ -0,0 +1,11 @@
+---
+title: KM0400B: ERROR_PackageCannotContainBothModelsAndKeyboards
+---
+
+| | |
+|------------|---------- |
+| Message | The package contains both lexical models and keyboards, which is not permitted\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_PackageCannotContainBothModelsAndKeyboards` |
+
+
diff --git a/developer/docs/help/reference/messages/km0400c.md b/developer/docs/help/reference/messages/km0400c.md
new file mode 100644
index 00000000000..e8534620b82
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0400c.md
@@ -0,0 +1,11 @@
+---
+title: KM0400C: HINT_PackageShouldNotRepeatLanguages
+---
+
+| | |
+|------------|---------- |
+| Message | Two language tags in <param> <param>, '<param>' and '<param>', reduce to the same minimal tag '<param>'\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `HINT_PackageShouldNotRepeatLanguages` |
+
+
diff --git a/developer/docs/help/reference/messages/km0400d.md b/developer/docs/help/reference/messages/km0400d.md
new file mode 100644
index 00000000000..b472719e0a9
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0400d.md
@@ -0,0 +1,11 @@
+---
+title: KM0400D: WARN_PackageNameDoesNotFollowLexicalModelConventions
+---
+
+| | |
+|------------|---------- |
+| Message | The package file <param> does not follow the recommended model filename conventions\. The name should be all lower case, include only alphanumeric characters and underscore \(\_\), not start with a digit, and should have the structure <author>\.<bcp47>\.<uniq>\.model\.kps\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `WARN_PackageNameDoesNotFollowLexicalModelConventions` |
+
+
diff --git a/developer/docs/help/reference/messages/km0400e.md b/developer/docs/help/reference/messages/km0400e.md
new file mode 100644
index 00000000000..6315015e312
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0400e.md
@@ -0,0 +1,11 @@
+---
+title: KM0400E: WARN_PackageNameDoesNotFollowKeyboardConventions
+---
+
+| | |
+|------------|---------- |
+| Message | The package file <param> does not follow the recommended keyboard filename conventions\. The name should be all lower case, include only alphanumeric characters and underscore \(\_\), and not start with a digit\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `WARN_PackageNameDoesNotFollowKeyboardConventions` |
+
+
diff --git a/developer/docs/help/reference/messages/km0400f.md b/developer/docs/help/reference/messages/km0400f.md
new file mode 100644
index 00000000000..654373479b1
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0400f.md
@@ -0,0 +1,11 @@
+---
+title: KM0400F: WARN_FileInPackageDoesNotFollowFilenameConventions
+---
+
+| | |
+|------------|---------- |
+| Message | The file <param> does not follow the recommended filename conventions\. The extension should be all lower case, and the filename should include only alphanumeric characters, \-, \_, \+ and \. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `WARN_FileInPackageDoesNotFollowFilenameConventions` |
+
+
diff --git a/developer/docs/help/reference/messages/km04010.md b/developer/docs/help/reference/messages/km04010.md
new file mode 100644
index 00000000000..dae0bc2ebc7
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04010.md
@@ -0,0 +1,11 @@
+---
+title: KM04010: ERROR_PackageNameCannotBeBlank
+---
+
+| | |
+|------------|---------- |
+| Message | Package name cannot be an empty string\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_PackageNameCannotBeBlank` |
+
+
diff --git a/developer/docs/help/reference/messages/km04011.md b/developer/docs/help/reference/messages/km04011.md
new file mode 100644
index 00000000000..c1244105ce9
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04011.md
@@ -0,0 +1,11 @@
+---
+title: KM04011: ERROR_KeyboardFileNotFound
+---
+
+| | |
+|------------|---------- |
+| Message | Keyboard file <param> was not found\. Has it been compiled? |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_KeyboardFileNotFound` |
+
+
diff --git a/developer/docs/help/reference/messages/km04012.md b/developer/docs/help/reference/messages/km04012.md
new file mode 100644
index 00000000000..42d3197b4aa
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04012.md
@@ -0,0 +1,11 @@
+---
+title: KM04012: WARN_KeyboardVersionsDoNotMatch
+---
+
+| | |
+|------------|---------- |
+| Message | Keyboard <param> version <param> does not match keyboard <param> version <param>\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `WARN_KeyboardVersionsDoNotMatch` |
+
+
diff --git a/developer/docs/help/reference/messages/km04014.md b/developer/docs/help/reference/messages/km04014.md
new file mode 100644
index 00000000000..df8a014a85b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04014.md
@@ -0,0 +1,11 @@
+---
+title: KM04014: ERROR_LanguageTagIsNotValid
+---
+
+| | |
+|------------|---------- |
+| Message | Language tag '<param>' in <param> <param> is invalid\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_LanguageTagIsNotValid` |
+
+
diff --git a/developer/docs/help/reference/messages/km04015.md b/developer/docs/help/reference/messages/km04015.md
new file mode 100644
index 00000000000..38d6a73b3fb
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04015.md
@@ -0,0 +1,11 @@
+---
+title: KM04015: HINT_LanguageTagIsNotMinimal
+---
+
+| | |
+|------------|---------- |
+| Message | Language tag '<param>' in <param> <param> is not minimal, and should be '<param>'\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `HINT_LanguageTagIsNotMinimal` |
+
+
diff --git a/developer/docs/help/reference/messages/km04016.md b/developer/docs/help/reference/messages/km04016.md
new file mode 100644
index 00000000000..5b4a6004097
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04016.md
@@ -0,0 +1,11 @@
+---
+title: KM04016: ERROR_ModelMustHaveAtLeastOneLanguage
+---
+
+| | |
+|------------|---------- |
+| Message | The lexical model <param> must have at least one language specified\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_ModelMustHaveAtLeastOneLanguage` |
+
+
diff --git a/developer/docs/help/reference/messages/km04017.md b/developer/docs/help/reference/messages/km04017.md
new file mode 100644
index 00000000000..8545103e8e0
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04017.md
@@ -0,0 +1,11 @@
+---
+title: KM04017: WARN_RedistFileShouldNotBeInPackage
+---
+
+| | |
+|------------|---------- |
+| Message | The Keyman system file '<param>' should not be compiled into the package\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `WARN_RedistFileShouldNotBeInPackage` |
+
+
diff --git a/developer/docs/help/reference/messages/km04018.md b/developer/docs/help/reference/messages/km04018.md
new file mode 100644
index 00000000000..7b80134b406
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04018.md
@@ -0,0 +1,11 @@
+---
+title: KM04018: WARN_DocFileDangerous
+---
+
+| | |
+|------------|---------- |
+| Message | Microsoft Word \.doc or \.docx files \('<param>'\) are not portable\. You should instead use HTML or PDF format\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `WARN_DocFileDangerous` |
+
+
diff --git a/developer/docs/help/reference/messages/km04019.md b/developer/docs/help/reference/messages/km04019.md
new file mode 100644
index 00000000000..ba36824e99b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04019.md
@@ -0,0 +1,11 @@
+---
+title: KM04019: ERROR_PackageMustContainAModelOrAKeyboard
+---
+
+| | |
+|------------|---------- |
+| Message | Package must contain a lexical model or a keyboard\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_PackageMustContainAModelOrAKeyboard` |
+
+
diff --git a/developer/docs/help/reference/messages/km0401a.md b/developer/docs/help/reference/messages/km0401a.md
new file mode 100644
index 00000000000..c26768212a9
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0401a.md
@@ -0,0 +1,11 @@
+---
+title: KM0401A: WARN_JsKeyboardFileIsMissing
+---
+
+| | |
+|------------|---------- |
+| Message | Keyboard <param> targets touch devices but corresponding <param>\.js file is not in the package\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `WARN_JsKeyboardFileIsMissing` |
+
+
diff --git a/developer/docs/help/reference/messages/km0401b.md b/developer/docs/help/reference/messages/km0401b.md
new file mode 100644
index 00000000000..2cb156f927c
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0401b.md
@@ -0,0 +1,11 @@
+---
+title: KM0401B: WARN_KeyboardShouldHaveAtLeastOneLanguage
+---
+
+| | |
+|------------|---------- |
+| Message | The keyboard <param> should have at least one language specified\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `WARN_KeyboardShouldHaveAtLeastOneLanguage` |
+
+
diff --git a/developer/docs/help/reference/messages/km0401c.md b/developer/docs/help/reference/messages/km0401c.md
new file mode 100644
index 00000000000..72c4c84404b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0401c.md
@@ -0,0 +1,11 @@
+---
+title: KM0401C: HINT_JsKeyboardFileHasNoTouchTargets
+---
+
+| | |
+|------------|---------- |
+| Message | The keyboard <param> has been included for touch platforms, but does not include a touch layout\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `HINT_JsKeyboardFileHasNoTouchTargets` |
+
+
diff --git a/developer/docs/help/reference/messages/km0401d.md b/developer/docs/help/reference/messages/km0401d.md
new file mode 100644
index 00000000000..d6a773dbed8
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0401d.md
@@ -0,0 +1,11 @@
+---
+title: KM0401D: HINT_PackageContainsSourceFile
+---
+
+| | |
+|------------|---------- |
+| Message | The source file <param> should not be included in the package; instead include the compiled result\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `HINT_PackageContainsSourceFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km0401e.md b/developer/docs/help/reference/messages/km0401e.md
new file mode 100644
index 00000000000..c7221243e5a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0401e.md
@@ -0,0 +1,11 @@
+---
+title: KM0401E: ERROR_InvalidPackageFile
+---
+
+| | |
+|------------|---------- |
+| Message | Package source file is invalid: unknown error |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_InvalidPackageFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km0401f.md b/developer/docs/help/reference/messages/km0401f.md
new file mode 100644
index 00000000000..71ffb82d91a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0401f.md
@@ -0,0 +1,11 @@
+---
+title: KM0401F: ERROR_FileRecordIsMissingName
+---
+
+| | |
+|------------|---------- |
+| Message | File record in the package with description 'undefined' is missing a filename\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_FileRecordIsMissingName` |
+
+
diff --git a/developer/docs/help/reference/messages/km04020.md b/developer/docs/help/reference/messages/km04020.md
new file mode 100644
index 00000000000..94de1c5805b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04020.md
@@ -0,0 +1,11 @@
+---
+title: KM04020: ERROR_InvalidAuthorEmail
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid author email: <param> |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_InvalidAuthorEmail` |
+
+
diff --git a/developer/docs/help/reference/messages/km04021.md b/developer/docs/help/reference/messages/km04021.md
new file mode 100644
index 00000000000..37b60e128e2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04021.md
@@ -0,0 +1,11 @@
+---
+title: KM04021: ERROR_PackageFileHasEmptyVersion
+---
+
+| | |
+|------------|---------- |
+| Message | Package version is not following keyboard version, but the package version field is blank\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_PackageFileHasEmptyVersion` |
+
+
diff --git a/developer/docs/help/reference/messages/km05001.md b/developer/docs/help/reference/messages/km05001.md
new file mode 100644
index 00000000000..8d6217d8266
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05001.md
@@ -0,0 +1,11 @@
+---
+title: KM05001: FATAL_UnexpectedException
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `FATAL_UnexpectedException` |
+
+
diff --git a/developer/docs/help/reference/messages/km05002.md b/developer/docs/help/reference/messages/km05002.md
new file mode 100644
index 00000000000..72b5f230ffe
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05002.md
@@ -0,0 +1,11 @@
+---
+title: KM05002: INFO_BuildingFile
+---
+
+| | |
+|------------|---------- |
+| Message | Building <param> |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `INFO_BuildingFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km05003.md b/developer/docs/help/reference/messages/km05003.md
new file mode 100644
index 00000000000..9c592343ee9
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05003.md
@@ -0,0 +1,11 @@
+---
+title: KM05003: ERROR_FileDoesNotExist
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> does not exist |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_FileDoesNotExist` |
+
+
diff --git a/developer/docs/help/reference/messages/km05004.md b/developer/docs/help/reference/messages/km05004.md
new file mode 100644
index 00000000000..613b1f7b9a0
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05004.md
@@ -0,0 +1,11 @@
+---
+title: KM05004: ERROR_FileTypeNotRecognized
+---
+
+| | |
+|------------|---------- |
+| Message | Unrecognised input file <param>, expecting <param>, or project folder |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_FileTypeNotRecognized` |
+
+
diff --git a/developer/docs/help/reference/messages/km05005.md b/developer/docs/help/reference/messages/km05005.md
new file mode 100644
index 00000000000..b8fd53b3c8e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05005.md
@@ -0,0 +1,11 @@
+---
+title: KM05005: ERROR_OutFileNotValidForProjects
+---
+
+| | |
+|------------|---------- |
+| Message | \-\-out\-file should not be specified for project builds |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_OutFileNotValidForProjects` |
+
+
diff --git a/developer/docs/help/reference/messages/km05006.md b/developer/docs/help/reference/messages/km05006.md
new file mode 100644
index 00000000000..65ba2240482
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05006.md
@@ -0,0 +1,11 @@
+---
+title: KM05006: INFO_FileBuiltSuccessfully
+---
+
+| | |
+|------------|---------- |
+| Message | <param> built successfully\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `INFO_FileBuiltSuccessfully` |
+
+
diff --git a/developer/docs/help/reference/messages/km05007.md b/developer/docs/help/reference/messages/km05007.md
new file mode 100644
index 00000000000..64f57f6e328
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05007.md
@@ -0,0 +1,11 @@
+---
+title: KM05007: INFO_FileNotBuiltSuccessfully
+---
+
+| | |
+|------------|---------- |
+| Message | <param> failed to build\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `INFO_FileNotBuiltSuccessfully` |
+
+
diff --git a/developer/docs/help/reference/messages/km05008.md b/developer/docs/help/reference/messages/km05008.md
new file mode 100644
index 00000000000..d0838674b32
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05008.md
@@ -0,0 +1,11 @@
+---
+title: KM05008: ERROR_InvalidProjectFile
+---
+
+| | |
+|------------|---------- |
+| Message | Project file is not valid: <param> |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_InvalidProjectFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km05009.md b/developer/docs/help/reference/messages/km05009.md
new file mode 100644
index 00000000000..1874676a98a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05009.md
@@ -0,0 +1,11 @@
+---
+title: KM05009: HINT_FilenameHasDifferingCase
+---
+
+| | |
+|------------|---------- |
+| Message | File on disk '<param>' does not match case of '<param>' in source file; this is an error on platforms with case\-sensitive filesystems\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `HINT_FilenameHasDifferingCase` |
+
+
diff --git a/developer/docs/help/reference/messages/km0500a.md b/developer/docs/help/reference/messages/km0500a.md
new file mode 100644
index 00000000000..c9dfa42d6c5
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0500a.md
@@ -0,0 +1,11 @@
+---
+title: KM0500A: ERROR_UnknownFileFormat
+---
+
+| | |
+|------------|---------- |
+| Message | Unknown file format <param>; only Markdown \(\.md\), JSON \(\.json\), and Text \(\.txt\) are supported\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_UnknownFileFormat` |
+
+
diff --git a/developer/docs/help/reference/messages/km0500b.md b/developer/docs/help/reference/messages/km0500b.md
new file mode 100644
index 00000000000..632bccaf1b5
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0500b.md
@@ -0,0 +1,11 @@
+---
+title: KM0500B: INFO_ProjectBuiltSuccessfully
+---
+
+| | |
+|------------|---------- |
+| Message | Project <param> built successfully\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `INFO_ProjectBuiltSuccessfully` |
+
+
diff --git a/developer/docs/help/reference/messages/km0500c.md b/developer/docs/help/reference/messages/km0500c.md
new file mode 100644
index 00000000000..9ed37722733
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0500c.md
@@ -0,0 +1,11 @@
+---
+title: KM0500C: INFO_ProjectNotBuiltSuccessfully
+---
+
+| | |
+|------------|---------- |
+| Message | Project <param> failed to build\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `INFO_ProjectNotBuiltSuccessfully` |
+
+
diff --git a/developer/docs/help/reference/messages/km0500d.md b/developer/docs/help/reference/messages/km0500d.md
new file mode 100644
index 00000000000..b3b7e0ae9e6
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0500d.md
@@ -0,0 +1,11 @@
+---
+title: KM0500D: INFO_TooManyMessages
+---
+
+| | |
+|------------|---------- |
+| Message | More than <param> warnings or errors received; suppressing further messages\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `INFO_TooManyMessages` |
+
+
diff --git a/developer/docs/help/reference/messages/km0500e.md b/developer/docs/help/reference/messages/km0500e.md
new file mode 100644
index 00000000000..1e8eecfe5c0
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0500e.md
@@ -0,0 +1,11 @@
+---
+title: KM0500E: ERROR_FileTypeNotFound
+---
+
+| | |
+|------------|---------- |
+| Message | A file of type <param> was not found in the project\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_FileTypeNotFound` |
+
+
diff --git a/developer/docs/help/reference/messages/km0500f.md b/developer/docs/help/reference/messages/km0500f.md
new file mode 100644
index 00000000000..b3ae8ebbef1
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0500f.md
@@ -0,0 +1,11 @@
+---
+title: KM0500F: ERROR_NotAProjectFile
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> must have a \.kpj extension to be treated as a project\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_NotAProjectFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km05010.md b/developer/docs/help/reference/messages/km05010.md
new file mode 100644
index 00000000000..18c0968ac6a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05010.md
@@ -0,0 +1,11 @@
+---
+title: KM05010: INFO_WarningsHaveFailedBuild
+---
+
+| | |
+|------------|---------- |
+| Message | The build failed because option "treat warnings as errors" is enabled and there are one or more warnings\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `INFO_WarningsHaveFailedBuild` |
+
+
diff --git a/developer/docs/help/reference/messages/km05011.md b/developer/docs/help/reference/messages/km05011.md
new file mode 100644
index 00000000000..f019f9e88f2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05011.md
@@ -0,0 +1,11 @@
+---
+title: KM05011: ERROR_CannotCreateFolder
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_CannotCreateFolder` |
+
+
diff --git a/developer/docs/help/reference/messages/km05012.md b/developer/docs/help/reference/messages/km05012.md
new file mode 100644
index 00000000000..19bcaf883a7
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05012.md
@@ -0,0 +1,11 @@
+---
+title: KM05012: ERROR_InvalidProjectFolder
+---
+
+| | |
+|------------|---------- |
+| Message | The folder <param> does not appear to be a Keyman Developer project\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_InvalidProjectFolder` |
+
+
diff --git a/developer/docs/help/reference/messages/km05013.md b/developer/docs/help/reference/messages/km05013.md
new file mode 100644
index 00000000000..bb5d07cf987
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05013.md
@@ -0,0 +1,11 @@
+---
+title: KM05013: ERROR_UnsupportedProjectVersion
+---
+
+| | |
+|------------|---------- |
+| Message | Project version <param> is not supported by this version of Keyman Developer\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_UnsupportedProjectVersion` |
+
+
diff --git a/developer/docs/help/reference/messages/km05014.md b/developer/docs/help/reference/messages/km05014.md
new file mode 100644
index 00000000000..3e812653143
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05014.md
@@ -0,0 +1,11 @@
+---
+title: KM05014: HINT_ProjectIsVersion10
+---
+
+| | |
+|------------|---------- |
+| Message | The project file is an older version and can be upgraded to version 17\.0 |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `HINT_ProjectIsVersion10` |
+
+
diff --git a/developer/docs/help/reference/messages/km05015.md b/developer/docs/help/reference/messages/km05015.md
new file mode 100644
index 00000000000..1807eba3d2c
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05015.md
@@ -0,0 +1,11 @@
+---
+title: KM05015: ERROR_OutFileCanOnlyBeSpecifiedWithSingleInfile
+---
+
+| | |
+|------------|---------- |
+| Message | Parameter \-\-out\-file can only be used with a single input file\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_OutFileCanOnlyBeSpecifiedWithSingleInfile` |
+
+
diff --git a/developer/docs/help/reference/messages/km05016.md b/developer/docs/help/reference/messages/km05016.md
new file mode 100644
index 00000000000..714d56acad1
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05016.md
@@ -0,0 +1,11 @@
+---
+title: KM05016: ERROR_InvalidMessageFormat
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid parameter: \-\-message <param> must match format '\[KM\]\#\#\#\#\#\[:Disable\|Info\|Hint\|Warn\|Error\]' |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_InvalidMessageFormat` |
+
+
diff --git a/developer/docs/help/reference/messages/km05017.md b/developer/docs/help/reference/messages/km05017.md
new file mode 100644
index 00000000000..660f4471536
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05017.md
@@ -0,0 +1,11 @@
+---
+title: KM05017: ERROR_MessageNamespaceNotFound
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid parameter: \-\-message <param> does not have a recognized namespace |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_MessageNamespaceNotFound` |
+
+
diff --git a/developer/docs/help/reference/messages/km05018.md b/developer/docs/help/reference/messages/km05018.md
new file mode 100644
index 00000000000..8c9056d6a51
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05018.md
@@ -0,0 +1,11 @@
+---
+title: KM05018: ERROR_MessageCodeNotFound
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid parameter: \-\-message undefined is not a recognized code |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_MessageCodeNotFound` |
+
+
diff --git a/developer/docs/help/reference/messages/km05019.md b/developer/docs/help/reference/messages/km05019.md
new file mode 100644
index 00000000000..352928390f1
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05019.md
@@ -0,0 +1,11 @@
+---
+title: KM05019: ERROR_MessageCannotBeCoerced
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid parameter: \-\-message <param> is not of type 'info', 'hint' or 'warn', and cannot be coerced |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_MessageCannotBeCoerced` |
+
+
diff --git a/developer/docs/help/reference/messages/km0501a.md b/developer/docs/help/reference/messages/km0501a.md
new file mode 100644
index 00000000000..0d1f3872233
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0501a.md
@@ -0,0 +1,11 @@
+---
+title: KM0501A: ERROR_UnrecognizedMessageCode
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid parameter: message identifier '<param>' must match format '\[KM\]\#\#\#\#\#' |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_UnrecognizedMessageCode` |
+
+
diff --git a/developer/docs/help/reference/messages/km0501b.md b/developer/docs/help/reference/messages/km0501b.md
new file mode 100644
index 00000000000..233e92f33c1
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0501b.md
@@ -0,0 +1,11 @@
+---
+title: KM0501B: ERROR_MustSpecifyMessageCode
+---
+
+| | |
+|------------|---------- |
+| Message | Must specify at least one message code or \-a for all messages |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_MustSpecifyMessageCode` |
+
+
diff --git a/developer/docs/help/reference/messages/km0501c.md b/developer/docs/help/reference/messages/km0501c.md
new file mode 100644
index 00000000000..510799f520b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0501c.md
@@ -0,0 +1,11 @@
+---
+title: KM0501C: ERROR_MessagesCannotBeFilteredForMarkdownFormat
+---
+
+| | |
+|------------|---------- |
+| Message | Messages cannot be filtered for markdown format |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_MessagesCannotBeFilteredForMarkdownFormat` |
+
+
diff --git a/developer/docs/help/reference/messages/km0501d.md b/developer/docs/help/reference/messages/km0501d.md
new file mode 100644
index 00000000000..c91d7897c21
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0501d.md
@@ -0,0 +1,11 @@
+---
+title: KM0501D: ERROR_OutputPathMustBeSpecifiedForMarkdownFormat
+---
+
+| | |
+|------------|---------- |
+| Message | Output path must be specified with \-o for markdown output format |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_OutputPathMustBeSpecifiedForMarkdownFormat` |
+
+
diff --git a/developer/docs/help/reference/messages/km0501e.md b/developer/docs/help/reference/messages/km0501e.md
new file mode 100644
index 00000000000..a4934b3856b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0501e.md
@@ -0,0 +1,11 @@
+---
+title: KM0501E: ERROR_OutputPathMustExistAndBeADirectory
+---
+
+| | |
+|------------|---------- |
+| Message | Output path <param> must exist and must be a folder |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_OutputPathMustExistAndBeADirectory` |
+
+
diff --git a/developer/docs/help/reference/messages/km06001.md b/developer/docs/help/reference/messages/km06001.md
new file mode 100644
index 00000000000..31e5c8afcfb
--- /dev/null
+++ b/developer/docs/help/reference/messages/km06001.md
@@ -0,0 +1,13 @@
+---
+title: KM06001: FATAL_UnexpectedException
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc-analyze.AnalyzerMessages](kmc-analyze.analyzermessages) |
+| Identifier | `FATAL_UnexpectedException` |
+
+Raised when an analysis components has an internal error. If you
+experience this error, it should be reported to the Keyman team for
+resolution via https://github.com/keymanapp/keyman/issues/new
diff --git a/developer/docs/help/reference/messages/km06002.md b/developer/docs/help/reference/messages/km06002.md
new file mode 100644
index 00000000000..b533ffd6675
--- /dev/null
+++ b/developer/docs/help/reference/messages/km06002.md
@@ -0,0 +1,11 @@
+---
+title: KM06002: INFO_ScanningFile
+---
+
+| | |
+|------------|---------- |
+| Message | Scanning <param> file <param> |
+| Module | [kmc-analyze.AnalyzerMessages](kmc-analyze.analyzermessages) |
+| Identifier | `INFO_ScanningFile` |
+
+Informative message reporting on the current file being scanned
diff --git a/developer/docs/help/reference/messages/km07001.md b/developer/docs/help/reference/messages/km07001.md
new file mode 100644
index 00000000000..05474815efc
--- /dev/null
+++ b/developer/docs/help/reference/messages/km07001.md
@@ -0,0 +1,11 @@
+---
+title: KM07001: ERROR_NotAnyRequiresVersion14
+---
+
+| | |
+|------------|---------- |
+| Message | Statement notany in context\(\) match requires version 14\.0\+ of KeymanWeb |
+| Module | [kmc-kmn.KmwCompilerMessages](kmc-kmn.kmwcompilermessages) |
+| Identifier | `ERROR_NotAnyRequiresVersion14` |
+
+
diff --git a/developer/docs/help/reference/messages/km07002.md b/developer/docs/help/reference/messages/km07002.md
new file mode 100644
index 00000000000..3725b73a769
--- /dev/null
+++ b/developer/docs/help/reference/messages/km07002.md
@@ -0,0 +1,11 @@
+---
+title: KM07002: ERROR_TouchLayoutIdentifierRequires15
+---
+
+| | |
+|------------|---------- |
+| Message | Key "<param>" on "<param>", layer "<param>" has a multi\-part identifier which requires version 15\.0 or newer\. |
+| Module | [kmc-kmn.KmwCompilerMessages](kmc-kmn.kmwcompilermessages) |
+| Identifier | `ERROR_TouchLayoutIdentifierRequires15` |
+
+
diff --git a/developer/docs/help/reference/messages/km07003.md b/developer/docs/help/reference/messages/km07003.md
new file mode 100644
index 00000000000..f248343f087
--- /dev/null
+++ b/developer/docs/help/reference/messages/km07003.md
@@ -0,0 +1,11 @@
+---
+title: KM07003: ERROR_InvalidTouchLayoutFileFormat
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid touch layout file: <param> |
+| Module | [kmc-kmn.KmwCompilerMessages](kmc-kmn.kmwcompilermessages) |
+| Identifier | `ERROR_InvalidTouchLayoutFileFormat` |
+
+
diff --git a/developer/docs/help/reference/messages/km07004.md b/developer/docs/help/reference/messages/km07004.md
new file mode 100644
index 00000000000..962ebd92f9b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km07004.md
@@ -0,0 +1,11 @@
+---
+title: KM07004: ERROR_TouchLayoutFileDoesNotExist
+---
+
+| | |
+|------------|---------- |
+| Message | Touch layout file <param> does not exist |
+| Module | [kmc-kmn.KmwCompilerMessages](kmc-kmn.kmwcompilermessages) |
+| Identifier | `ERROR_TouchLayoutFileDoesNotExist` |
+
+
diff --git a/developer/docs/help/reference/messages/km07005.md b/developer/docs/help/reference/messages/km07005.md
new file mode 100644
index 00000000000..852b8689757
--- /dev/null
+++ b/developer/docs/help/reference/messages/km07005.md
@@ -0,0 +1,11 @@
+---
+title: KM07005: HINT_TouchLayoutUsesUnsupportedGesturesDownlevel
+---
+
+| | |
+|------------|---------- |
+| Message | The touch layout uses a flick or multi\-tap gesture on key <param>, which is only available on version 17\.0\+ of Keyman |
+| Module | [kmc-kmn.KmwCompilerMessages](kmc-kmn.kmwcompilermessages) |
+| Identifier | `HINT_TouchLayoutUsesUnsupportedGesturesDownlevel` |
+
+
diff --git a/developer/docs/help/reference/messages/km08001.md b/developer/docs/help/reference/messages/km08001.md
new file mode 100644
index 00000000000..5387285f733
--- /dev/null
+++ b/developer/docs/help/reference/messages/km08001.md
@@ -0,0 +1,11 @@
+---
+title: KM08001: FATAL_UnexpectedException
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages) |
+| Identifier | `FATAL_UnexpectedException` |
+
+
diff --git a/developer/docs/help/reference/messages/km08002.md b/developer/docs/help/reference/messages/km08002.md
new file mode 100644
index 00000000000..77a251119aa
--- /dev/null
+++ b/developer/docs/help/reference/messages/km08002.md
@@ -0,0 +1,11 @@
+---
+title: KM08002: ERROR_FileDoesNotExist
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> does not exist\. |
+| Module | [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages) |
+| Identifier | `ERROR_FileDoesNotExist` |
+
+
diff --git a/developer/docs/help/reference/messages/km08003.md b/developer/docs/help/reference/messages/km08003.md
new file mode 100644
index 00000000000..ae9d7244d9f
--- /dev/null
+++ b/developer/docs/help/reference/messages/km08003.md
@@ -0,0 +1,11 @@
+---
+title: KM08003: ERROR_FileIsNotValid
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> could not be parsed: unknown error\. |
+| Module | [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages) |
+| Identifier | `ERROR_FileIsNotValid` |
+
+
diff --git a/developer/docs/help/reference/messages/km08004.md b/developer/docs/help/reference/messages/km08004.md
new file mode 100644
index 00000000000..82ad0635445
--- /dev/null
+++ b/developer/docs/help/reference/messages/km08004.md
@@ -0,0 +1,11 @@
+---
+title: KM08004: WARN_MetadataFieldInconsistent
+---
+
+| | |
+|------------|---------- |
+| Message | Warning: field <param> value "<param>" does not match "<param>" found in source file metadata\. |
+| Module | [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages) |
+| Identifier | `WARN_MetadataFieldInconsistent` |
+
+
diff --git a/developer/docs/help/reference/messages/km08005.md b/developer/docs/help/reference/messages/km08005.md
new file mode 100644
index 00000000000..b71b3590c1a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km08005.md
@@ -0,0 +1,11 @@
+---
+title: KM08005: ERROR_InvalidAuthorEmail
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid author email: <param> |
+| Module | [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages) |
+| Identifier | `ERROR_InvalidAuthorEmail` |
+
+
diff --git a/developer/docs/help/reference/messages/km08006.md b/developer/docs/help/reference/messages/km08006.md
new file mode 100644
index 00000000000..15e4997a403
--- /dev/null
+++ b/developer/docs/help/reference/messages/km08006.md
@@ -0,0 +1,11 @@
+---
+title: KM08006: ERROR_LicenseFileIsMissing
+---
+
+| | |
+|------------|---------- |
+| Message | License file <param> does not exist\. |
+| Module | [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages) |
+| Identifier | `ERROR_LicenseFileIsMissing` |
+
+
diff --git a/developer/docs/help/reference/messages/km08007.md b/developer/docs/help/reference/messages/km08007.md
new file mode 100644
index 00000000000..6b352d11109
--- /dev/null
+++ b/developer/docs/help/reference/messages/km08007.md
@@ -0,0 +1,11 @@
+---
+title: KM08007: ERROR_LicenseFileIsDamaged
+---
+
+| | |
+|------------|---------- |
+| Message | License file <param> could not be loaded or decoded\. |
+| Module | [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages) |
+| Identifier | `ERROR_LicenseFileIsDamaged` |
+
+
diff --git a/developer/docs/help/reference/messages/km08008.md b/developer/docs/help/reference/messages/km08008.md
new file mode 100644
index 00000000000..f7b5cfa3cef
--- /dev/null
+++ b/developer/docs/help/reference/messages/km08008.md
@@ -0,0 +1,11 @@
+---
+title: KM08008: ERROR_LicenseIsNotValid
+---
+
+| | |
+|------------|---------- |
+| Message | An error was encountered parsing license file <param>: <param>\. |
+| Module | [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages) |
+| Identifier | `ERROR_LicenseIsNotValid` |
+
+
diff --git a/developer/docs/help/reference/messages/km08009.md b/developer/docs/help/reference/messages/km08009.md
new file mode 100644
index 00000000000..969ba14c7b7
--- /dev/null
+++ b/developer/docs/help/reference/messages/km08009.md
@@ -0,0 +1,11 @@
+---
+title: KM08009: ERROR_NoLicenseFound
+---
+
+| | |
+|------------|---------- |
+| Message | No license for the model was found\. MIT license is required for publication to Keyman lexical\-models repository\. |
+| Module | [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages) |
+| Identifier | `ERROR_NoLicenseFound` |
+
+
diff --git a/developer/docs/help/reference/messages/km0800a.md b/developer/docs/help/reference/messages/km0800a.md
new file mode 100644
index 00000000000..2d6170b19af
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0800a.md
@@ -0,0 +1,11 @@
+---
+title: KM0800A: ERROR_DescriptionIsMissing
+---
+
+| | |
+|------------|---------- |
+| Message | The Info\.Description field in the package <param> is required, but is missing or empty\. |
+| Module | [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages) |
+| Identifier | `ERROR_DescriptionIsMissing` |
+
+
diff --git a/developer/docs/help/reference/messages/km09001.md b/developer/docs/help/reference/messages/km09001.md
new file mode 100644
index 00000000000..7012d6e5516
--- /dev/null
+++ b/developer/docs/help/reference/messages/km09001.md
@@ -0,0 +1,11 @@
+---
+title: KM09001: FATAL_UnexpectedException
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `FATAL_UnexpectedException` |
+
+
diff --git a/developer/docs/help/reference/messages/km09002.md b/developer/docs/help/reference/messages/km09002.md
new file mode 100644
index 00000000000..17882283d09
--- /dev/null
+++ b/developer/docs/help/reference/messages/km09002.md
@@ -0,0 +1,11 @@
+---
+title: KM09002: ERROR_FileDoesNotExist
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> does not exist\. |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `ERROR_FileDoesNotExist` |
+
+
diff --git a/developer/docs/help/reference/messages/km09005.md b/developer/docs/help/reference/messages/km09005.md
new file mode 100644
index 00000000000..d88e2440adb
--- /dev/null
+++ b/developer/docs/help/reference/messages/km09005.md
@@ -0,0 +1,11 @@
+---
+title: KM09005: ERROR_InvalidAuthorEmail
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid author email: <param> |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `ERROR_InvalidAuthorEmail` |
+
+
diff --git a/developer/docs/help/reference/messages/km09006.md b/developer/docs/help/reference/messages/km09006.md
new file mode 100644
index 00000000000..f8579775216
--- /dev/null
+++ b/developer/docs/help/reference/messages/km09006.md
@@ -0,0 +1,11 @@
+---
+title: KM09006: ERROR_LicenseFileIsMissing
+---
+
+| | |
+|------------|---------- |
+| Message | License file <param> does not exist\. |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `ERROR_LicenseFileIsMissing` |
+
+
diff --git a/developer/docs/help/reference/messages/km09007.md b/developer/docs/help/reference/messages/km09007.md
new file mode 100644
index 00000000000..bbab92c762e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km09007.md
@@ -0,0 +1,11 @@
+---
+title: KM09007: ERROR_LicenseFileIsDamaged
+---
+
+| | |
+|------------|---------- |
+| Message | License file <param> could not be loaded or decoded\. |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `ERROR_LicenseFileIsDamaged` |
+
+
diff --git a/developer/docs/help/reference/messages/km09008.md b/developer/docs/help/reference/messages/km09008.md
new file mode 100644
index 00000000000..bc6adb01a61
--- /dev/null
+++ b/developer/docs/help/reference/messages/km09008.md
@@ -0,0 +1,11 @@
+---
+title: KM09008: ERROR_LicenseIsNotValid
+---
+
+| | |
+|------------|---------- |
+| Message | An error was encountered parsing license file <param>: <param>\. |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `ERROR_LicenseIsNotValid` |
+
+
diff --git a/developer/docs/help/reference/messages/km09009.md b/developer/docs/help/reference/messages/km09009.md
new file mode 100644
index 00000000000..d8c08d3d6f1
--- /dev/null
+++ b/developer/docs/help/reference/messages/km09009.md
@@ -0,0 +1,11 @@
+---
+title: KM09009: ERROR_CannotBuildWithoutKmpFile
+---
+
+| | |
+|------------|---------- |
+| Message | Compiling the \.keyboard\_info file requires a \.kmp file for metadata\. |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `ERROR_CannotBuildWithoutKmpFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km0900a.md b/developer/docs/help/reference/messages/km0900a.md
new file mode 100644
index 00000000000..8623a4f8dbf
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0900a.md
@@ -0,0 +1,11 @@
+---
+title: KM0900A: ERROR_NoLicenseFound
+---
+
+| | |
+|------------|---------- |
+| Message | No license for the keyboard was found\. MIT license is required for publication to Keyman keyboards repository\. |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `ERROR_NoLicenseFound` |
+
+
diff --git a/developer/docs/help/reference/messages/km0900e.md b/developer/docs/help/reference/messages/km0900e.md
new file mode 100644
index 00000000000..a2ba732bb78
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0900e.md
@@ -0,0 +1,11 @@
+---
+title: KM0900E: ERROR_FontFileCannotBeRead
+---
+
+| | |
+|------------|---------- |
+| Message | Font <param> could not be parsed to extract a font family\. |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `ERROR_FontFileCannotBeRead` |
+
+
diff --git a/developer/docs/help/reference/messages/km0900f.md b/developer/docs/help/reference/messages/km0900f.md
new file mode 100644
index 00000000000..e2d5511d425
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0900f.md
@@ -0,0 +1,11 @@
+---
+title: KM0900F: ERROR_FontFileMetaDataIsInvalid
+---
+
+| | |
+|------------|---------- |
+| Message | Font <param> meta data invalid: <param>\. |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `ERROR_FontFileMetaDataIsInvalid` |
+
+
diff --git a/developer/docs/help/reference/messages/km09010.md b/developer/docs/help/reference/messages/km09010.md
new file mode 100644
index 00000000000..4fc25c8c367
--- /dev/null
+++ b/developer/docs/help/reference/messages/km09010.md
@@ -0,0 +1,11 @@
+---
+title: KM09010: ERROR_DescriptionIsMissing
+---
+
+| | |
+|------------|---------- |
+| Message | The Info\.Description field in the package <param> is required, but is missing or empty\. |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `ERROR_DescriptionIsMissing` |
+
+
diff --git a/developer/docs/help/reference/messages/kmc-analyze.analyzermessages.md b/developer/docs/help/reference/messages/kmc-analyze.analyzermessages.md
new file mode 100644
index 00000000000..a874853ba00
--- /dev/null
+++ b/developer/docs/help/reference/messages/kmc-analyze.analyzermessages.md
@@ -0,0 +1,8 @@
+---
+title: Compiler Messages Reference for @keymanapp/kmc-analyze
+---
+
+ Code | Identifier | Message
+------|------------|---------
+[KM06001](km06001) | `FATAL_UnexpectedException` | This is an internal error; the message will vary
+[KM06002](km06002) | `INFO_ScanningFile` | Scanning <param> file <param>
diff --git a/developer/docs/help/reference/messages/kmc-keyboard-info.keyboardinfocompilermessages.md b/developer/docs/help/reference/messages/kmc-keyboard-info.keyboardinfocompilermessages.md
new file mode 100644
index 00000000000..99c6bb97af9
--- /dev/null
+++ b/developer/docs/help/reference/messages/kmc-keyboard-info.keyboardinfocompilermessages.md
@@ -0,0 +1,17 @@
+---
+title: Compiler Messages Reference for @keymanapp/kmc-keyboard-info
+---
+
+ Code | Identifier | Message
+------|------------|---------
+[KM09001](km09001) | `FATAL_UnexpectedException` | This is an internal error; the message will vary
+[KM09002](km09002) | `ERROR_FileDoesNotExist` | File <param> does not exist\.
+[KM09005](km09005) | `ERROR_InvalidAuthorEmail` | Invalid author email: <param>
+[KM09006](km09006) | `ERROR_LicenseFileIsMissing` | License file <param> does not exist\.
+[KM09007](km09007) | `ERROR_LicenseFileIsDamaged` | License file <param> could not be loaded or decoded\.
+[KM09008](km09008) | `ERROR_LicenseIsNotValid` | An error was encountered parsing license file <param>: <param>\.
+[KM09009](km09009) | `ERROR_CannotBuildWithoutKmpFile` | Compiling the \.keyboard\_info file requires a \.kmp file for metadata\.
+[KM0900A](km0900a) | `ERROR_NoLicenseFound` | No license for the keyboard was found\. MIT license is required for publication to Keyman keyboards repository\.
+[KM0900E](km0900e) | `ERROR_FontFileCannotBeRead` | Font <param> could not be parsed to extract a font family\.
+[KM0900F](km0900f) | `ERROR_FontFileMetaDataIsInvalid` | Font <param> meta data invalid: <param>\.
+[KM09010](km09010) | `ERROR_DescriptionIsMissing` | The Info\.Description field in the package <param> is required, but is missing or empty\.
diff --git a/developer/docs/help/reference/messages/kmc-kmn.kmncompilermessages.md b/developer/docs/help/reference/messages/kmc-kmn.kmncompilermessages.md
new file mode 100644
index 00000000000..4d7d2eb542e
--- /dev/null
+++ b/developer/docs/help/reference/messages/kmc-kmn.kmncompilermessages.md
@@ -0,0 +1,177 @@
+---
+title: Compiler Messages Reference for @keymanapp/kmc-kmn
+---
+
+ Code | Identifier | Message
+------|------------|---------
+[KM02001](km02001) | `INFO_EndOfFile` | \(no error \- reserved code\)
+[KM02002](km02002) | `FATAL_BadCallParams` | CompileKeyboardFile was called with bad parameters
+[KM02004](km02004) | `FATAL_CannotAllocateMemory` | Out of memory
+[KM02005](km02005) | `ERROR_InfileNotExist` | Cannot find the input file
+[KM02007](km02007) | `FATAL_UnableToWriteFully` | Unable to write the file completely
+[KM02008](km02008) | `ERROR_CannotReadInfile` | Cannot read the input file
+[KM02009](km02009) | `FATAL_SomewhereIGotItWrong` | Internal error: contact Keyman
+[KM0200A](km0200a) | `ERROR_InvalidToken` | Invalid token found
+[KM0200B](km0200b) | `ERROR_InvalidBegin` | A "begin unicode" statement is required to compile a KeymanWeb keyboard
+[KM0200C](km0200c) | `ERROR_InvalidName` | Invalid 'name' command
+[KM0200D](km0200d) | `ERROR_InvalidVersion` | Invalid 'version' command
+[KM0200E](km0200e) | `ERROR_InvalidLanguageLine` | Invalid 'language' command
+[KM0200F](km0200f) | `ERROR_LayoutButNoLanguage` | Layout command found but no language command
+[KM02010](km02010) | `ERROR_InvalidLayoutLine` | Invalid 'layout' command
+[KM02011](km02011) | `ERROR_NoVersionLine` | No version line found for file
+[KM02012](km02012) | `ERROR_InvalidGroupLine` | Invalid 'group' command
+[KM02013](km02013) | `ERROR_InvalidStoreLine` | Invalid 'store' command
+[KM02014](km02014) | `ERROR_InvalidCodeInKeyPartOfRule` | Invalid command or code found in key part of rule
+[KM02015](km02015) | `ERROR_InvalidDeadkey` | Invalid 'deadkey' or 'dk' command
+[KM02016](km02016) | `ERROR_InvalidValue` | Invalid value in extended string
+[KM02017](km02017) | `ERROR_ZeroLengthString` | A string of zero characters was found
+[KM02018](km02018) | `ERROR_TooManyIndexToKeyRefs` | Too many index commands refering to key string
+[KM02019](km02019) | `ERROR_UnterminatedString` | Unterminated string in line
+[KM0201A](km0201a) | `ERROR_StringInVirtualKeySection` | extend string illegal in virtual key section
+[KM0201B](km0201b) | `ERROR_AnyInVirtualKeySection` | 'any' command is illegal in virtual key section
+[KM0201C](km0201c) | `ERROR_InvalidAny` | Invalid 'any' command
+[KM0201D](km0201d) | `ERROR_StoreDoesNotExist` | Store referenced does not exist
+[KM0201E](km0201e) | `ERROR_BeepInVirtualKeySection` | 'beep' command is illegal in virtual key section
+[KM0201F](km0201f) | `ERROR_IndexInVirtualKeySection` | 'index' command is illegal in virtual key section
+[KM02020](km02020) | `ERROR_InvalidIndex` | Invalid 'index' command
+[KM02021](km02021) | `ERROR_OutsInVirtualKeySection` | 'outs' command is illegal in virtual key section
+[KM02022](km02022) | `ERROR_InvalidOuts` | Invalid 'outs' command
+[KM02024](km02024) | `ERROR_ContextInVirtualKeySection` | 'context' command is illegal in virtual key section
+[KM02025](km02025) | `ERROR_InvalidUse` | Invalid 'use' command
+[KM02026](km02026) | `ERROR_GroupDoesNotExist` | Group does not exist
+[KM02027](km02027) | `ERROR_VirtualKeyNotAllowedHere` | Virtual key is not allowed here
+[KM02028](km02028) | `ERROR_InvalidSwitch` | Invalid 'switch' command
+[KM02029](km02029) | `ERROR_NoTokensFound` | No tokens found in line
+[KM0202A](km0202a) | `ERROR_InvalidLineContinuation` | Invalid line continuation
+[KM0202B](km0202b) | `ERROR_LineTooLong` | Line too long
+[KM0202C](km0202c) | `ERROR_InvalidCopyright` | Invalid 'copyright' command
+[KM0202D](km0202d) | `ERROR_CodeInvalidInThisSection` | This line is invalid in this section of the file
+[KM0202E](km0202e) | `ERROR_InvalidMessage` | Invalid 'message' command
+[KM0202F](km0202f) | `ERROR_InvalidLanguageName` | Invalid 'languagename' command
+[KM02030](km02030) | `ERROR_InvalidBitmapLine` | Invalid 'bitmaps' command
+[KM02031](km02031) | `ERROR_CannotReadBitmapFile` | Cannot open the bitmap or icon file for reading
+[KM02032](km02032) | `ERROR_IndexDoesNotPointToAny` | An index\(\) in the output does not have a corresponding any\(\) statement
+[KM02033](km02033) | `ERROR_ReservedCharacter` | A reserved character was found
+[KM02034](km02034) | `ERROR_InvalidCharacter` | A character was found that is outside the valid Unicode range \(U\+0000 \- U\+10FFFF\)
+[KM02035](km02035) | `ERROR_InvalidCall` | The 'call' command is invalid
+[KM02036](km02036) | `ERROR_CallInVirtualKeySection` | 'call' command is illegal in virtual key section
+[KM02037](km02037) | `ERROR_CodeInvalidInKeyStore` | The command is invalid inside a store that is used in a key part of the rule
+[KM02038](km02038) | `ERROR_CannotLoadIncludeFile` | Cannot load the included file: it is either invalid or does not exist
+[KM02039](km02039) | `ERROR_60FeatureOnly_EthnologueCode` | EthnologueCode system store requires VERSION 6\.0 or higher
+[KM0203A](km0203a) | `ERROR_60FeatureOnly_MnemonicLayout` | MnemonicLayout functionality requires VERSION 6\.0 or higher
+[KM0203B](km0203b) | `ERROR_60FeatureOnly_OldCharPosMatching` | OldCharPosMatching system store requires VERSION 6\.0 or higher
+[KM0203C](km0203c) | `ERROR_60FeatureOnly_NamedCodes` | Named character constants requires VERSION 6\.0 or higher
+[KM0203D](km0203d) | `ERROR_60FeatureOnly_Contextn` | Context\(n\) requires VERSION 6\.0 or higher
+[KM0203E](km0203e) | `ERROR_501FeatureOnly_Call` | Call\(\) requires VERSION 5\.01 or higher
+[KM0203F](km0203f) | `ERROR_InvalidNamedCode` | Invalid named code constant
+[KM02040](km02040) | `ERROR_InvalidSystemStore` | Invalid system store name found
+[KM02044](km02044) | `ERROR_60FeatureOnly_VirtualCharKey` | Virtual character keys require VERSION 6\.0 or higher
+[KM02045](km02045) | `ERROR_VersionAlreadyIncluded` | Only one VERSION or store\(version\) line allowed in a source file\.
+[KM02046](km02046) | `ERROR_70FeatureOnly` | This feature requires store\(version\) '7\.0' or higher
+[KM02047](km02047) | `ERROR_80FeatureOnly` | This feature requires store\(version\) '8\.0' or higher
+[KM02048](km02048) | `ERROR_InvalidInVirtualKeySection` | This statement is not valid in a virtual key section
+[KM02049](km02049) | `ERROR_InvalidIf` | The if\(\) statement is not valid
+[KM0204A](km0204a) | `ERROR_InvalidReset` | The reset\(\) statement is not valid
+[KM0204B](km0204b) | `ERROR_InvalidSet` | The set\(\) statement is not valid
+[KM0204C](km0204c) | `ERROR_InvalidSave` | The save\(\) statement is not valid
+[KM0204D](km0204d) | `ERROR_InvalidEthnologueCode` | Invalid ethnologuecode format
+[KM0204E](km0204e) | `FATAL_CannotCreateTempfile` | Cannot create temp file
+[KM0204F](km0204f) | `ERROR_90FeatureOnly_IfSystemStores` | if\(store\) requires store\(version\) '9\.0' or higher
+[KM02050](km02050) | `ERROR_IfSystemStore_NotFound` | System store in if\(\) not found
+[KM02051](km02051) | `ERROR_90FeatureOnly_SetSystemStores` | set\(store\) requires store\(version\) '9\.0' or higher
+[KM02052](km02052) | `ERROR_SetSystemStore_NotFound` | System store in set\(\) not found
+[KM02053](km02053) | `ERROR_90FeatureOnlyVirtualKeyDictionary` | Custom virtual key names require store\(version\) '9\.0'
+[KM02054](km02054) | `ERROR_NotSupportedInKeymanWebContext` | Statement '<param>' is not currently supported in context for web and touch targets
+[KM02055](km02055) | `ERROR_NotSupportedInKeymanWebOutput` | Statement '<param>' is not currently supported in output for web and touch targets
+[KM02056](km02056) | `ERROR_NotSupportedInKeymanWebStore` | '<param>' is not currently supported in store '<param>' when used by any or index for web and touch targets
+[KM02057](km02057) | `ERROR_VirtualCharacterKeysNotSupportedInKeymanWeb` | Virtual character keys not currently supported in KeymanWeb
+[KM02058](km02058) | `ERROR_VirtualKeysNotValidForMnemonicLayouts` | Virtual keys are not valid for mnemonic layouts
+[KM02059](km02059) | `ERROR_InvalidTouchLayoutFile` | Touch layout file <param> is not valid
+[KM0205A](km0205a) | `ERROR_TouchLayoutInvalidIdentifier` | Key "<param>" on "<param>", layer "<param>" has an invalid identifier\.
+[KM0205B](km0205b) | `ERROR_InvalidKeyCode` | Invalid key identifier "<param>"
+[KM0205C](km0205c) | `ERROR_90FeatureOnlyLayoutFile` | Touch layout file reference requires store\(version\) '9\.0'or higher
+[KM0205D](km0205d) | `ERROR_90FeatureOnlyKeyboardVersion` | KeyboardVersion system store requires store\(version\) '9\.0'or higher
+[KM0205E](km0205e) | `ERROR_KeyboardVersionFormatInvalid` | KeyboardVersion format is invalid, expecting dot\-separated integers
+[KM0205F](km0205f) | `ERROR_ContextExHasInvalidOffset` | context\(\) statement has offset out of range
+[KM02060](km02060) | `ERROR_90FeatureOnlyEmbedCSS` | Embedding CSS requires store\(version\) '9\.0'or higher
+[KM02061](km02061) | `ERROR_90FeatureOnlyTargets` | TARGETS system store requires store\(version\) '9\.0'or higher
+[KM02062](km02062) | `ERROR_ContextAndIndexInvalidInMatchNomatch` | context and index statements cannot be used in a match or nomatch statement
+[KM02063](km02063) | `ERROR_140FeatureOnlyContextAndNotAnyWeb` | For web and touch platforms, context\(\) statement referring to notany\(\) requires store\(version\) '14\.0'or higher
+[KM02064](km02064) | `ERROR_ExpansionMustFollowCharacterOrVKey` | An expansion must follow a character or a virtual key
+[KM02065](km02065) | `ERROR_VKeyExpansionMustBeFollowedByVKey` | A virtual key expansion must be terminated by a virtual key
+[KM02066](km02066) | `ERROR_CharacterExpansionMustBeFollowedByCharacter` | A character expansion must be terminated by a character key
+[KM02067](km02067) | `ERROR_VKeyExpansionMustUseConsistentShift` | A virtual key expansion must use the same shift state for both terminators
+[KM02068](km02068) | `ERROR_ExpansionMustBePositive` | An expansion must have positive difference \(i\.e\. A\-Z, not Z\-A\)
+[KM02069](km02069) | `ERROR_CasedKeysMustContainOnlyVirtualKeys` | The &CasedKeys system store must contain only virtual keys or characters found on a US English keyboard
+[KM0206A](km0206a) | `ERROR_CasedKeysMustNotIncludeShiftStates` | The &CasedKeys system store must not include shift states
+[KM0206B](km0206b) | `ERROR_CasedKeysNotSupportedWithMnemonicLayout` | The &CasedKeys system store is not supported with mnemonic layouts
+[KM0206C](km0206c) | `ERROR_CannotUseReadWriteGroupFromReadonlyGroup` | Group used from a readonly group must also be readonly
+[KM0206D](km0206d) | `ERROR_StatementNotPermittedInReadonlyGroup` | Statement is not permitted in output of readonly group
+[KM0206E](km0206e) | `ERROR_OutputInReadonlyGroup` | Output is not permitted in a readonly group
+[KM0206F](km0206f) | `ERROR_NewContextGroupMustBeReadonly` | Group used in begin newContext must be readonly
+[KM02070](km02070) | `ERROR_PostKeystrokeGroupMustBeReadonly` | Group used in begin postKeystroke must be readonly
+[KM02071](km02071) | `ERROR_DuplicateGroup` | A group with this name has already been defined\.
+[KM02072](km02072) | `ERROR_DuplicateStore` | A store with this name has already been defined\.
+[KM02073](km02073) | `ERROR_RepeatedBegin` | Begin has already been set
+[KM02074](km02074) | `ERROR_VirtualKeyInContext` | Virtual keys are not permitted in context
+[KM02075](km02075) | `ERROR_OutsTooLong` | Store cannot be inserted with outs\(\) as it makes the extended string too long
+[KM02076](km02076) | `ERROR_ExtendedStringTooLong` | Extended string is too long
+[KM02077](km02077) | `ERROR_VirtualKeyExpansionTooLong` | Virtual key expansion is too large
+[KM02078](km02078) | `ERROR_CharacterRangeTooLong` | Character range is too large and cannot be expanded
+[KM02079](km02079) | `ERROR_NonBMPCharactersNotSupportedInKeySection` | Characters with codepoints over U\+FFFF are not supported in the key part of the rule
+[KM02080](km02080) | `WARN_TooManyWarnings` | Too many warnings or errors
+[KM02081](km02081) | `WARN_OldVersion` | The keyboard file is an old version
+[KM02082](km02082) | `WARN_BitmapNotUsed` | The 'bitmaps' statement is obsolete and only the first bitmap referred to will be used, you should use 'bitmap'\.
+[KM02083](km02083) | `WARN_CustomLanguagesNotSupported` | Languages over 0x1FF, 0x1F are not supported correctly by Windows\. You should use no LANGUAGE line instead\.
+[KM02084](km02084) | `WARN_KeyBadLength` | There are too many characters in the keystroke part of the rule\.
+[KM02085](km02085) | `WARN_IndexStoreShort` | The store referenced in index\(\) is shorter than the store referenced in any\(\)
+[KM02086](km02086) | `WARN_UnicodeInANSIGroup` | A Unicode character was found in an ANSI group
+[KM02087](km02087) | `WARN_ANSIInUnicodeGroup` | An ANSI character was found in a Unicode group
+[KM02088](km02088) | `WARN_UnicodeSurrogateUsed` | A Unicode surrogate character was found\. You should use Unicode scalar values to represent values > U\+FFFF
+[KM02089](km02089) | `WARN_ReservedCharacter` | A Unicode character was found that should not be used
+[KM0208A](km0208a) | `INFO_Info` | Information
+[KM0208B](km0208b) | `WARN_VirtualKeyWithMnemonicLayout` | Virtual key used instead of virtual character key with a mnemonic layout
+[KM0208C](km0208c) | `WARN_VirtualCharKeyWithPositionalLayout` | Virtual character key used with a positional layout instead of mnemonic layout
+[KM0208D](km0208d) | `WARN_StoreAlreadyUsedAsOptionOrCall` | Store already used as an option or in a call statement and should not be used as a normal store
+[KM0208E](km0208e) | `WARN_StoreAlreadyUsedAsStoreOrCall` | Store already used as a normal store or in a call statement and should not be used as an option
+[KM0208F](km0208f) | `WARN_StoreAlreadyUsedAsStoreOrOption` | Store already used as a normal store or as an option and should not be used in a call statement
+[KM02090](km02090) | `WARN_PunctuationInEthnologueCode` | Punctuation should not be used to separate Ethnologue codes; instead use spaces
+[KM02091](km02091) | `WARN_TouchLayoutMissingLayer` | Key "<param>" on platform "<param>", layer "<param>", references a missing layer "<param>"
+[KM02092](km02092) | `WARN_TouchLayoutCustomKeyNotDefined` | Key "<param>" on platform "<param>", layer "<param>", is a custom key but has no corresponding rule in the source\.
+[KM02093](km02093) | `WARN_TouchLayoutMissingRequiredKeys` | Layer "<param>" on platform "<param>" is missing the required key\(s\) '<param>'\.
+[KM02094](km02094) | `WARN_HelpFileMissing` | File <param> could not be loaded:
+[KM02095](km02095) | `WARN_EmbedJsFileMissing` | File <param> could not be loaded:
+[KM02098](km02098) | `WARN_ExtendedShiftFlagsNotSupportedInKeymanWeb` | Extended shift flags <param> are not supported in KeymanWeb
+[KM02099](km02099) | `WARN_TouchLayoutUnidentifiedKey` | A key on layer "<param>" has no identifier\.
+[KM0209A](km0209a) | `HINT_UnreachableKeyCode` | The rule will never be matched for key <param> because its key code is never fired\.
+[KM0209C](km0209c) | `WARN_PlatformNotInTargets` | The specified platform is not a target platform
+[KM0209D](km0209d) | `WARN_HeaderStatementIsDeprecated` | Header statements are deprecated; use instead the equivalent system store
+[KM0209E](km0209e) | `WARN_UseNotLastStatementInRule` | A rule with use\(\) statements in the output should not have other content following the use\(\) statements
+[KM0209F](km0209f) | `WARN_TouchLayoutFontShouldBeSameForAllPlatforms` | The touch layout font should be the same for all platforms\.
+[KM020A2](km020a2) | `WARN_KVKFileIsInSourceFormat` | \.kvk file should be binary but is an XML file
+[KM020A3](km020a3) | `WARN_DontMixChiralAndNonChiralModifiers` | This keyboard contains Ctrl,Alt and LCtrl,LAlt,RCtrl,RAlt sets of modifiers\. Use only one or the other set for web target\.
+[KM020A4](km020a4) | `WARN_MixingLeftAndRightModifiers` | Left and right modifiers should not both be used in the same rule
+[KM020A5](km020a5) | `WARN_LanguageHeadersDeprecatedInKeyman10` | This language header has been deprecated in Keyman 10\. Instead, add language metadata in the package file
+[KM020A6](km020a6) | `HINT_NonUnicodeFile` | Keyman Developer has detected that the file has ANSI encoding\. Consider converting this file to UTF\-8
+[KM020A8](km020a8) | `WARN_HotkeyHasInvalidModifier` | Hotkey has modifiers that are not supported\. Use only SHIFT, CTRL and ALT
+[KM020A9](km020a9) | `WARN_TouchLayoutSpecialLabelOnNormalKey` | Key "<param>" on platform "<param>", layer "<param>" does not have the key type "Special" or "Special \(active\)" but has the label "<param>"\. This feature is only supported in Keyman 14 or later
+[KM020AA](km020aa) | `WARN_OptionStoreNameInvalid` | The option store <param> should be named with characters in the range A\-Z, a\-z, 0\-9 and \_ only\.
+[KM020AB](km020ab) | `WARN_NulNotFirstStatementInContext` | nul must be the first statement in the context
+[KM020AC](km020ac) | `WARN_IfShouldBeAtStartOfContext` | if, platform and baselayout should be at start of context \(after nul, if present\)
+[KM020AD](km020ad) | `WARN_KeyShouldIncludeNCaps` | Other rules which reference this key include CAPS or NCAPS modifiers, so this rule must include NCAPS modifier to avoid inconsistent matches
+[KM020AE](km020ae) | `HINT_UnreachableRule` | This rule will never be matched as another rule takes precedence
+[KM020AF](km020af) | `WARN_VirtualKeyInOutput` | Virtual keys are not supported in output
+[KM020C0](km020c0) | `FATAL_BufferOverflow` | The compiler memory buffer overflowed
+[KM020C1](km020c1) | `FATAL_Break` | Compiler interrupted by user
+[KM02900](km02900) | `FATAL_UnexpectedException` | This is an internal error; the message will vary
+[KM02901](km02901) | `FATAL_MissingWasmModule` | This is an internal error; the message will vary
+[KM02903](km02903) | `FATAL_CallbacksNotSet` | This is an internal error; the message will vary
+[KM02904](km02904) | `FATAL_UnicodeSetOutOfRange` | This is an internal error; the message will vary
+[KM02905](km02905) | `ERROR_UnicodeSetHasStrings` | uset contains strings, not allowed
+[KM02906](km02906) | `ERROR_UnicodeSetHasProperties` | uset contains properties, not allowed
+[KM02907](km02907) | `ERROR_UnicodeSetSyntaxError` | uset had a Syntax Error while parsing
+[KM02908](km02908) | `ERROR_InvalidKvksFile` | Error encountered parsing <param>: unknown error
+[KM02909](km02909) | `WARN_InvalidVkeyInKvksFile` | Invalid virtual key <param> found in <param>
+[KM0290A](km0290a) | `ERROR_InvalidDisplayMapFile` | Error encountered parsing display map <param>: unknown error
+[KM0290B](km0290b) | `ERROR_InvalidKvkFile` | Error encountered loading <param>: unknown error
+[KM0290C](km0290c) | `ERROR_FileNotFound` | File <param> was not found
diff --git a/developer/docs/help/reference/messages/kmc-kmn.kmwcompilermessages.md b/developer/docs/help/reference/messages/kmc-kmn.kmwcompilermessages.md
new file mode 100644
index 00000000000..84c21e4a170
--- /dev/null
+++ b/developer/docs/help/reference/messages/kmc-kmn.kmwcompilermessages.md
@@ -0,0 +1,11 @@
+---
+title: Compiler Messages Reference for @keymanapp/kmc-kmn
+---
+
+ Code | Identifier | Message
+------|------------|---------
+[KM07001](km07001) | `ERROR_NotAnyRequiresVersion14` | Statement notany in context\(\) match requires version 14\.0\+ of KeymanWeb
+[KM07002](km07002) | `ERROR_TouchLayoutIdentifierRequires15` | Key "<param>" on "<param>", layer "<param>" has a multi\-part identifier which requires version 15\.0 or newer\.
+[KM07003](km07003) | `ERROR_InvalidTouchLayoutFileFormat` | Invalid touch layout file: <param>
+[KM07004](km07004) | `ERROR_TouchLayoutFileDoesNotExist` | Touch layout file <param> does not exist
+[KM07005](km07005) | `HINT_TouchLayoutUsesUnsupportedGesturesDownlevel` | The touch layout uses a flick or multi\-tap gesture on key <param>, which is only available on version 17\.0\+ of Keyman
diff --git a/developer/docs/help/reference/messages/kmc-ldml.compilermessages.md b/developer/docs/help/reference/messages/kmc-ldml.compilermessages.md
new file mode 100644
index 00000000000..46dbf4e2e9c
--- /dev/null
+++ b/developer/docs/help/reference/messages/kmc-ldml.compilermessages.md
@@ -0,0 +1,49 @@
+---
+title: Compiler Messages Reference for @keymanapp/kmc-ldml
+---
+
+ Code | Identifier | Message
+------|------------|---------
+[KM00001](km00001) | `HINT_NormalizationDisabled` | normalization=disabled is not recommended\.
+[KM00002](km00002) | `ERROR_InvalidLocale` | Invalid BCP 47 locale form '<param>'
+[KM00003](km00003) | `ERROR_HardwareLayerHasTooManyRows` | 'hardware' layer has too many rows
+[KM00004](km00004) | `ERROR_RowOnHardwareLayerHasTooManyKeys` | Row \#<param> on 'hardware' <param> layer for modifier none has too many keys
+[KM00005](km00005) | `ERROR_KeyNotFoundInKeyBag` | Key '<param>' in position \#<param> on row \#<param> of layer <param>, form '<param>' not found in key bag
+[KM00006](km00006) | `HINT_OneOrMoreRepeatedLocales` | After minimization, one or more locales is repeated and has been removed
+[KM00007](km00007) | `ERROR_InvalidFile` | The source file has an invalid structure: <param>
+[KM00008](km00008) | `HINT_LocaleIsNotMinimalAndClean` | Locale '<param>' is not minimal or correctly formatted and should be '<param>'
+[KM00009](km00009) | `ERROR_InvalidScanCode` | Form '<param>' has invalid/unknown scancodes '<param>'
+[KM0000A](km0000a) | `WARN_CustomForm` | Custom <form id="<param>"> element\. Key layout may not be as expected\.
+[KM0000B](km0000b) | `ERROR_GestureKeyNotFoundInKeyBag` | Key '<param>' not found in key bag, referenced from other '<param>' in <param>
+[KM0000D](km0000d) | `ERROR_InvalidVersion` | Version number '<param>' must be a semantic version format string\.
+[KM0000E](km0000e) | `ERROR_MustBeAtLeastOneLayerElement` | The source file must contain at least one layer element\.
+[KM00010](km00010) | `ERROR_DisplayIsRepeated` | display has more than one display entry\.
+[KM00011](km00011) | `ERROR_KeyMissingToGapOrSwitch` | key id='<param>' must have either output=, gap=, or layerId=\.
+[KM00012](km00012) | `ERROR_ExcessHardware` | layers formId=<param>: Can only have one non\-'touch' element
+[KM00013](km00013) | `ERROR_InvalidHardware` | layers has invalid value formId=<param>
+[KM00014](km00014) | `ERROR_InvalidModifier` | layer has invalid modifiers='<param>' on layer id=<param>
+[KM00015](km00015) | `ERROR_MissingFlicks` | key id=<param> refers to missing flickId=<param>
+[KM00016](km00016) | `ERROR_DuplicateVariable` | duplicate variables: id=<param>
+[KM00018](km00018) | `ERROR_InvalidTransformsType` | Invalid transforms types: '<param>'
+[KM00019](km00019) | `ERROR_DuplicateTransformsType` | Duplicate transforms types: '<param>'
+[KM0001A](km0001a) | `ERROR_MixedTransformGroup` | transformGroup cannot contain both reorder and transform elements
+[KM0001B](km0001b) | `ERROR_EmptyTransformGroup` | transformGroup must have either reorder or transform elements
+[KM0001C](km0001c) | `ERROR_MissingStringVariable` | Reference to undefined string variable: $\{<param>\}
+[KM0001D](km0001d) | `ERROR_MissingSetVariable` | Reference to undefined set variable: $\[<param>\]
+[KM0001E](km0001e) | `ERROR_MissingUnicodeSetVariable` | Reference to undefined UnicodeSet variable: $\[<param>\]
+[KM0001F](km0001f) | `ERROR_NeedSpacesBetweenSetVariables` | Need spaces between set variables: <param>
+[KM00020](km00020) | `ERROR_CantReferenceSetFromUnicodeSet` | Illegal use of set variable from within UnicodeSet: $\[<param>\]
+[KM00021](km00021) | `ERROR_MissingMarkers` | Markers used for matching but not defined: <param>
+[KM00022](km00022) | `ERROR_DisplayNeedsToOrId` | display needs output= or keyId=, but not both
+[KM00023](km00023) | `HINT_PUACharacters` | File contains <param> PUA character\(s\), including Illegal \(U\+NAN\)
+[KM00024](km00024) | `WARN_UnassignedCharacters` | File contains <param> unassigned character\(s\), including Illegal \(U\+NAN\)
+[KM00025](km00025) | `ERROR_IllegalCharacters` | File contains <param> illegal character\(s\), including Illegal \(U\+NAN\)
+[KM00026](km00026) | `HINT_CharClassImplicitDenorm` | File has character classes which span non\-NFD character\(s\), including Illegal \(U\+NAN\)\. These will not match any text\.
+[KM00027](km00027) | `WARN_CharClassExplicitDenorm` | File has character classes which include non\-NFD characters\(s\), including Illegal \(U\+NAN\)\. These will not match any text\.
+[KM00028](km00028) | `ERROR_UnparseableReorderSet` | Illegal UnicodeSet "<param>" in reorder "<param>
+[KM00030](km00030) | `ERROR_InvalidQuadEscape` | Invalid escape "\\u0000"\. Hint: Use "\\u\{<param>\}"
+[KM00F00](km00f00) | `ERROR_UnparseableTransformFrom` | Invalid transform from="<param>": "<param>"
+[KM00F01](km00f01) | `ERROR_IllegalTransformDollarsign` | Invalid transform from="<param>": Unescaped dollar\-sign \($\) is not valid transform syntax\.
+[KM00F02](km00f02) | `ERROR_TransformFromMatchesNothing` | Invalid transfom from="<param>": Matches an empty string\.
+[KM00F03](km00f03) | `ERROR_IllegalTransformPlus` | Invalid transform from="<param>": Unescaped plus \(\+\) is not valid transform syntax\.
+[KM00F04](km00f04) | `ERROR_IllegalTransformAsterisk` | Invalid transform from="<param>": Unescaped asterisk \(\*\) is not valid transform syntax\.
diff --git a/developer/docs/help/reference/messages/kmc-model-info.modelinfocompilermessages.md b/developer/docs/help/reference/messages/kmc-model-info.modelinfocompilermessages.md
new file mode 100644
index 00000000000..61837405eb4
--- /dev/null
+++ b/developer/docs/help/reference/messages/kmc-model-info.modelinfocompilermessages.md
@@ -0,0 +1,16 @@
+---
+title: Compiler Messages Reference for @keymanapp/kmc-model-info
+---
+
+ Code | Identifier | Message
+------|------------|---------
+[KM08001](km08001) | `FATAL_UnexpectedException` | This is an internal error; the message will vary
+[KM08002](km08002) | `ERROR_FileDoesNotExist` | File <param> does not exist\.
+[KM08003](km08003) | `ERROR_FileIsNotValid` | File <param> could not be parsed: unknown error\.
+[KM08004](km08004) | `WARN_MetadataFieldInconsistent` | Warning: field <param> value "<param>" does not match "<param>" found in source file metadata\.
+[KM08005](km08005) | `ERROR_InvalidAuthorEmail` | Invalid author email: <param>
+[KM08006](km08006) | `ERROR_LicenseFileIsMissing` | License file <param> does not exist\.
+[KM08007](km08007) | `ERROR_LicenseFileIsDamaged` | License file <param> could not be loaded or decoded\.
+[KM08008](km08008) | `ERROR_LicenseIsNotValid` | An error was encountered parsing license file <param>: <param>\.
+[KM08009](km08009) | `ERROR_NoLicenseFound` | No license for the model was found\. MIT license is required for publication to Keyman lexical\-models repository\.
+[KM0800A](km0800a) | `ERROR_DescriptionIsMissing` | The Info\.Description field in the package <param> is required, but is missing or empty\.
diff --git a/developer/docs/help/reference/messages/kmc-model.modelcompilermessages.md b/developer/docs/help/reference/messages/kmc-model.modelcompilermessages.md
new file mode 100644
index 00000000000..aaa55ec3bb7
--- /dev/null
+++ b/developer/docs/help/reference/messages/kmc-model.modelcompilermessages.md
@@ -0,0 +1,16 @@
+---
+title: Compiler Messages Reference for @keymanapp/kmc-model
+---
+
+ Code | Identifier | Message
+------|------------|---------
+[KM03001](km03001) | `FATAL_UnexpectedException` | This is an internal error; the message will vary
+[KM03002](km03002) | `HINT_MixedNormalizationForms` | “<param>” is not in Unicode NFC\. Automatically converting to NFC\.
+[KM03003](km03003) | `HINT_DuplicateWordInSameFile` | duplicate word “<param>” found in same file; summing counts
+[KM03004](km03004) | `ERROR_UnimplementedModelFormat` | Unimplemented model format: <param>
+[KM03005](km03005) | `ERROR_UnknownModelFormat` | Unimplemented model format: <param>
+[KM03006](km03006) | `ERROR_NoDefaultExport` | Model source does have a default export\. Did you remember to write \`export default source;\`?
+[KM03007](km03007) | `ERROR_SearchTermToKeyMustBeExplicitlySpecified` | searchTermToKey must be explicitly specified
+[KM03008](km03008) | `ERROR_UTF16BEUnsupported` | UTF\-16BE is unsupported
+[KM03009](km03009) | `ERROR_UnknownWordBreaker` | Unknown word breaker: <param>
+[KM0300A](km0300a) | `ERROR_UnsupportedScriptOverride` | Unsupported script override: <param>
diff --git a/developer/docs/help/reference/messages/kmc-package.compilermessages.md b/developer/docs/help/reference/messages/kmc-package.compilermessages.md
new file mode 100644
index 00000000000..627fd5860ad
--- /dev/null
+++ b/developer/docs/help/reference/messages/kmc-package.compilermessages.md
@@ -0,0 +1,38 @@
+---
+title: Compiler Messages Reference for @keymanapp/kmc-package
+---
+
+ Code | Identifier | Message
+------|------------|---------
+[KM04001](km04001) | `FATAL_UnexpectedException` | This is an internal error; the message will vary
+[KM04002](km04002) | `WARN_AbsolutePath` | File <param> has an absolute path, which is not portable\.
+[KM04003](km04003) | `ERROR_FileDoesNotExist` | File <param> does not exist\.
+[KM04004](km04004) | `ERROR_FileCouldNotBeRead` | File <param> could not be read: unknown error\.
+[KM04005](km04005) | `WARN_FileIsNotABinaryKvkFile` | File <param> does not appear to be a valid binary \.kvk file; this may be an old package that includes an xml\-format \.kvk file\. You must update the package to include the compiled \.kvk file in the package\.
+[KM04006](km04006) | `ERROR_FollowKeyboardVersionNotAllowedForModelPackages` | FollowKeyboardVersion is not allowed in model packages
+[KM04007](km04007) | `ERROR_FollowKeyboardVersionButNoKeyboards` | FollowKeyboardVersion is set, but the package contains no keyboards
+[KM04008](km04008) | `ERROR_KeyboardContentFileNotFound` | Keyboard <param> was listed in <Keyboards> but a corresponding \.kmx file was not found in <Files>
+[KM04009](km04009) | `ERROR_KeyboardFileNotValid` | Keyboard file <param> is not a valid \.kmx file: unknown error
+[KM0400A](km0400a) | `INFO_KeyboardFileHasNoKeyboardVersion` | Keyboard file <param> has no &KeyboardVersion store, using default '0\.0'
+[KM0400B](km0400b) | `ERROR_PackageCannotContainBothModelsAndKeyboards` | The package contains both lexical models and keyboards, which is not permitted\.
+[KM0400C](km0400c) | `HINT_PackageShouldNotRepeatLanguages` | Two language tags in <param> <param>, '<param>' and '<param>', reduce to the same minimal tag '<param>'\.
+[KM0400D](km0400d) | `WARN_PackageNameDoesNotFollowLexicalModelConventions` | The package file <param> does not follow the recommended model filename conventions\. The name should be all lower case, include only alphanumeric characters and underscore \(\_\), not start with a digit, and should have the structure <author>\.<bcp47>\.<uniq>\.model\.kps\.
+[KM0400E](km0400e) | `WARN_PackageNameDoesNotFollowKeyboardConventions` | The package file <param> does not follow the recommended keyboard filename conventions\. The name should be all lower case, include only alphanumeric characters and underscore \(\_\), and not start with a digit\.
+[KM0400F](km0400f) | `WARN_FileInPackageDoesNotFollowFilenameConventions` | The file <param> does not follow the recommended filename conventions\. The extension should be all lower case, and the filename should include only alphanumeric characters, \-, \_, \+ and \.
+[KM04010](km04010) | `ERROR_PackageNameCannotBeBlank` | Package name cannot be an empty string\.
+[KM04011](km04011) | `ERROR_KeyboardFileNotFound` | Keyboard file <param> was not found\. Has it been compiled?
+[KM04012](km04012) | `WARN_KeyboardVersionsDoNotMatch` | Keyboard <param> version <param> does not match keyboard <param> version <param>\.
+[KM04014](km04014) | `ERROR_LanguageTagIsNotValid` | Language tag '<param>' in <param> <param> is invalid\.
+[KM04015](km04015) | `HINT_LanguageTagIsNotMinimal` | Language tag '<param>' in <param> <param> is not minimal, and should be '<param>'\.
+[KM04016](km04016) | `ERROR_ModelMustHaveAtLeastOneLanguage` | The lexical model <param> must have at least one language specified\.
+[KM04017](km04017) | `WARN_RedistFileShouldNotBeInPackage` | The Keyman system file '<param>' should not be compiled into the package\.
+[KM04018](km04018) | `WARN_DocFileDangerous` | Microsoft Word \.doc or \.docx files \('<param>'\) are not portable\. You should instead use HTML or PDF format\.
+[KM04019](km04019) | `ERROR_PackageMustContainAModelOrAKeyboard` | Package must contain a lexical model or a keyboard\.
+[KM0401A](km0401a) | `WARN_JsKeyboardFileIsMissing` | Keyboard <param> targets touch devices but corresponding <param>\.js file is not in the package\.
+[KM0401B](km0401b) | `WARN_KeyboardShouldHaveAtLeastOneLanguage` | The keyboard <param> should have at least one language specified\.
+[KM0401C](km0401c) | `HINT_JsKeyboardFileHasNoTouchTargets` | The keyboard <param> has been included for touch platforms, but does not include a touch layout\.
+[KM0401D](km0401d) | `HINT_PackageContainsSourceFile` | The source file <param> should not be included in the package; instead include the compiled result\.
+[KM0401E](km0401e) | `ERROR_InvalidPackageFile` | Package source file is invalid: unknown error
+[KM0401F](km0401f) | `ERROR_FileRecordIsMissingName` | File record in the package with description 'undefined' is missing a filename\.
+[KM04020](km04020) | `ERROR_InvalidAuthorEmail` | Invalid author email: <param>
+[KM04021](km04021) | `ERROR_PackageFileHasEmptyVersion` | Package version is not following keyboard version, but the package version field is blank\.
diff --git a/developer/docs/help/reference/messages/kmc.infrastructuremessages.md b/developer/docs/help/reference/messages/kmc.infrastructuremessages.md
new file mode 100644
index 00000000000..ce80ed88852
--- /dev/null
+++ b/developer/docs/help/reference/messages/kmc.infrastructuremessages.md
@@ -0,0 +1,36 @@
+---
+title: Compiler Messages Reference for @keymanapp/kmc
+---
+
+ Code | Identifier | Message
+------|------------|---------
+[KM05001](km05001) | `FATAL_UnexpectedException` | This is an internal error; the message will vary
+[KM05002](km05002) | `INFO_BuildingFile` | Building <param>
+[KM05003](km05003) | `ERROR_FileDoesNotExist` | File <param> does not exist
+[KM05004](km05004) | `ERROR_FileTypeNotRecognized` | Unrecognised input file <param>, expecting <param>, or project folder
+[KM05005](km05005) | `ERROR_OutFileNotValidForProjects` | \-\-out\-file should not be specified for project builds
+[KM05006](km05006) | `INFO_FileBuiltSuccessfully` | <param> built successfully\.
+[KM05007](km05007) | `INFO_FileNotBuiltSuccessfully` | <param> failed to build\.
+[KM05008](km05008) | `ERROR_InvalidProjectFile` | Project file is not valid: <param>
+[KM05009](km05009) | `HINT_FilenameHasDifferingCase` | File on disk '<param>' does not match case of '<param>' in source file; this is an error on platforms with case\-sensitive filesystems\.
+[KM0500A](km0500a) | `ERROR_UnknownFileFormat` | Unknown file format <param>; only Markdown \(\.md\), JSON \(\.json\), and Text \(\.txt\) are supported\.
+[KM0500B](km0500b) | `INFO_ProjectBuiltSuccessfully` | Project <param> built successfully\.
+[KM0500C](km0500c) | `INFO_ProjectNotBuiltSuccessfully` | Project <param> failed to build\.
+[KM0500D](km0500d) | `INFO_TooManyMessages` | More than <param> warnings or errors received; suppressing further messages\.
+[KM0500E](km0500e) | `ERROR_FileTypeNotFound` | A file of type <param> was not found in the project\.
+[KM0500F](km0500f) | `ERROR_NotAProjectFile` | File <param> must have a \.kpj extension to be treated as a project\.
+[KM05010](km05010) | `INFO_WarningsHaveFailedBuild` | The build failed because option "treat warnings as errors" is enabled and there are one or more warnings\.
+[KM05011](km05011) | `ERROR_CannotCreateFolder` | This is an internal error; the message will vary
+[KM05012](km05012) | `ERROR_InvalidProjectFolder` | The folder <param> does not appear to be a Keyman Developer project\.
+[KM05013](km05013) | `ERROR_UnsupportedProjectVersion` | Project version <param> is not supported by this version of Keyman Developer\.
+[KM05014](km05014) | `HINT_ProjectIsVersion10` | The project file is an older version and can be upgraded to version 17\.0
+[KM05015](km05015) | `ERROR_OutFileCanOnlyBeSpecifiedWithSingleInfile` | Parameter \-\-out\-file can only be used with a single input file\.
+[KM05016](km05016) | `ERROR_InvalidMessageFormat` | Invalid parameter: \-\-message <param> must match format '\[KM\]\#\#\#\#\#\[:Disable\|Info\|Hint\|Warn\|Error\]'
+[KM05017](km05017) | `ERROR_MessageNamespaceNotFound` | Invalid parameter: \-\-message <param> does not have a recognized namespace
+[KM05018](km05018) | `ERROR_MessageCodeNotFound` | Invalid parameter: \-\-message undefined is not a recognized code
+[KM05019](km05019) | `ERROR_MessageCannotBeCoerced` | Invalid parameter: \-\-message <param> is not of type 'info', 'hint' or 'warn', and cannot be coerced
+[KM0501A](km0501a) | `ERROR_UnrecognizedMessageCode` | Invalid parameter: message identifier '<param>' must match format '\[KM\]\#\#\#\#\#'
+[KM0501B](km0501b) | `ERROR_MustSpecifyMessageCode` | Must specify at least one message code or \-a for all messages
+[KM0501C](km0501c) | `ERROR_MessagesCannotBeFilteredForMarkdownFormat` | Messages cannot be filtered for markdown format
+[KM0501D](km0501d) | `ERROR_OutputPathMustBeSpecifiedForMarkdownFormat` | Output path must be specified with \-o for markdown output format
+[KM0501E](km0501e) | `ERROR_OutputPathMustExistAndBeADirectory` | Output path <param> must exist and must be a folder
diff --git a/developer/docs/help/reference/tools/index.md b/developer/docs/help/reference/tools/index.md
new file mode 100644
index 00000000000..2dd2d36ef82
--- /dev/null
+++ b/developer/docs/help/reference/tools/index.md
@@ -0,0 +1,7 @@
+---
+title: Keyman Developer Tools
+---
+
+* [Setup Bootstrapper](setup)
+* [kmc command line compiler](../kmc)
+* [kmconvert keyboard conversion tool](../../context/kmconvert)
diff --git a/developer/docs/help/reference/tools/setup.md b/developer/docs/help/reference/tools/setup.md
new file mode 100644
index 00000000000..d628634f064
--- /dev/null
+++ b/developer/docs/help/reference/tools/setup.md
@@ -0,0 +1,17 @@
+---
+title: Setup Bootstrapper
+---
+
+# Setup Bootstrapper
+
+The Keyman Desktop installation bootstrapper is a self-extracting executable file that contains a Windows Installer technology MSI file, and optionally keyboards. The bootstrapper is used whenever a package installer is created.
+
+The bootstrapper:
+
+1. Checks the system before starting the installation to ensure that all prerequisites are met, and optionally downloads and installs the prerequisites.
+2. Optionally checks online for an updated version of Keyman Desktop before installing.
+3. Starts the Keyman Desktop Windows Installer MSI package.
+4. Installs any keyboards included.
+5. Starts Keyman Desktop after the install completes.
+
+For command line parameters and more information see [Knowledge Base article KMKB0035](/kb/35)
diff --git a/developer/docs/help/reference/user-settings.md b/developer/docs/help/reference/user-settings.md
new file mode 100644
index 00000000000..8652c5c98bd
--- /dev/null
+++ b/developer/docs/help/reference/user-settings.md
@@ -0,0 +1,28 @@
+---
+title: Keyman Developer User Settings
+---
+
+Keyman Developer has settings stored in several locations:
+
+`~/.keymandeveloper/options.json` (`%userprofile%\.keymandeveloper\options.json` on Windows)
+
+: User preferences are stored in this file. In version 16 and earlier, these
+ options were stored in the Registry under
+ `HKCU\Software\Keyman\Keyman Developer\IDE\Options`. Any options found in
+ the Registry will be migrated to the `options.json` file on first start
+ of the Keyman Developer IDE.
+
+`HKCU\Software\Keyman\Keyman Developer\IDE`
+
+: Stores settings specific to the IDE, including window locations and
+ visibility, recently used projects, font preferences.
+
+`%appdata%\Keyman\Keyman Developer`
+
+: Stores files related to the runtime environment of Keyman Developer, including
+ cache files.
+
+`%localappdata%\Keyman\Diag`
+
+: Stores files relating to diagnostics for Keyman Developer; this folder is also
+ used for Keyman for Windows diagnostics.
diff --git a/developer/docs/help/whats-new.md b/developer/docs/help/whats-new.md
new file mode 100644
index 00000000000..d1dce092c37
--- /dev/null
+++ b/developer/docs/help/whats-new.md
@@ -0,0 +1,43 @@
+---
+title: What's new in Keyman Developer 18.0
+---
+
+Keyman Developer 17 has a number of significant changes:
+
+* `kmcomp` has been removed and replaced with `kmc`. Learn more in our [kmcomp migration guide](reference/kmc/cli/kmcomp-migration).
+
+* Windows installer packages should now be built with
+ `kmc build windows-package-installer` and can no longer be built within
+ the Keyman Developer IDE. More details on how to use this are in the
+ [`kmc` reference documentation](reference/kmc/cli/reference#toc-kmc-build-windows-package-installer-additional-options).
+
+* `.keyboard_info` files are now generated entirely from the package and
+ keyboard files. Extra fields are available in packages for license file,
+ welcome file, typing examples, related packages (including deprecated
+ packages), and additional font files. Keyboards in the Keyman Cloud
+ keyboard repository have already been updated; if you are an author of
+ one of these keyboards, you should pull the changes from the repository
+ before submitting future updates.
+
+* Keyboard project files (.kpj) now have a new format which does not list
+ individual files, but instead includes all files in the same folder and
+ subfolders relative to the project file. This prevents projects from
+ including unrelated files, and reduces the number of files that change
+ when keyboards are updated.
+
+* Keyman Developer will now open each project in a new window, allowing
+ the user to rapidly switch between multiple projects at the same time.
+
+* The Keyman Keyboard and Lexical Model Cloud repositories have new build
+ scripts which run `kmc`. These run much, much faster than previously!
+
+* The [`&displayMap`](/developer/language/reference/displaymap) store allows
+ keyboard developers to specify a font mapping to PUA for the On Screen
+ Keyboard and Touch Layout to resolve diacritic rendering issues.
+
+* Keyman Developer has been updated to support Unicode 15.1.
+
+* Additional non-printing characters have been added to the Touch Layout Editor.
+
+* Virtual keys in output of rules have never worked properly or been officially
+ supported; Keyman Developer will now warn if you attempt to use this pattern.
diff --git a/developer/src/build.sh b/developer/src/build.sh
index b45dea38f50..cde9b7fc233 100755
--- a/developer/src/build.sh
+++ b/developer/src/build.sh
@@ -149,9 +149,9 @@ function do_publish() {
DRY_RUN=--dry-run
fi
+ ./common/web/utils/build.sh publish $DRY_RUN $NPM_PUBLISH
../../common/web/keyman-version/build.sh publish $DRY_RUN $NPM_PUBLISH
../../common/web/types/build.sh publish $DRY_RUN $NPM_PUBLISH
- ../../common/models/types/build.sh publish $DRY_RUN $NPM_PUBLISH
../../core/include/ldml/build.sh publish $DRY_RUN $NPM_PUBLISH
# end TODO
#--------------------------------------------------------
diff --git a/developer/src/common/web/utils/package.json b/developer/src/common/web/utils/package.json
index 1ab9daddbf8..7f2d9cef279 100644
--- a/developer/src/common/web/utils/package.json
+++ b/developer/src/common/web/utils/package.json
@@ -9,19 +9,19 @@
"/build/"
],
"dependencies": {
- "@sentry/node": "^7.57.0",
"@keymanapp/common-types": "*",
+ "@sentry/node": "^7.57.0",
"eventemitter3": "^5.0.0",
+ "fast-xml-parser": "^4.5.0",
"restructure": "^3.0.1",
- "semver": "^7.5.4",
"sax": ">=0.6.0",
+ "semver": "^7.5.4",
"xmlbuilder": "~11.0.0"
},
"devDependencies": {
"@types/git-diff": "^2.0.3",
"@types/node": "^20.4.1",
"@types/semver": "^7.3.12",
- "@types/xml2js": "^0.4.5",
"c8": "^7.12.0",
"git-diff": "^2.0.6",
"mocha": "^8.4.0",
diff --git a/developer/src/common/web/utils/src/common-events.ts b/developer/src/common/web/utils/src/common-messages.ts
similarity index 91%
rename from developer/src/common/web/utils/src/common-events.ts
rename to developer/src/common/web/utils/src/common-messages.ts
index eee8a6c84ca..5b3fd7d5070 100644
--- a/developer/src/common/web/utils/src/common-events.ts
+++ b/developer/src/common/web/utils/src/common-messages.ts
@@ -1,3 +1,6 @@
+/*
+ * Keyman is copyright (C) SIL International. MIT License.
+ */
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageDef as def, CompilerMessageSpec as m } from './compiler-interfaces.js';
import { constants } from '@keymanapp/ldml-keyboard-constants';
@@ -43,4 +46,8 @@ export class CommonTypesMessages {
static Error_TestDataUnexpectedArray = (o: {subtag: string}) =>
m(this.ERROR_TestDataUnexpectedArray,
`Problem reading test data: expected single ${def(o.subtag)} element, found multiple`);
+
+ static ERROR_InvalidXml = SevError | 0x0008;
+ static Error_InvalidXml = (o:{e: any}) =>
+ m(this.ERROR_InvalidXml, `The XML file could not be read: ${(o.e ?? '').toString()}`);
};
diff --git a/developer/src/common/web/utils/src/deps/xml2js/LICENSE b/developer/src/common/web/utils/src/deps/xml2js/LICENSE
deleted file mode 100644
index e3b4222a66a..00000000000
--- a/developer/src/common/web/utils/src/deps/xml2js/LICENSE
+++ /dev/null
@@ -1,19 +0,0 @@
-Copyright 2010, 2011, 2012, 2013. All rights reserved.
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to
-deal in the Software without restriction, including without limitation the
-rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-sell copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-IN THE SOFTWARE.
diff --git a/developer/src/common/web/utils/src/deps/xml2js/README.md b/developer/src/common/web/utils/src/deps/xml2js/README.md
deleted file mode 100644
index 67f2104a513..00000000000
--- a/developer/src/common/web/utils/src/deps/xml2js/README.md
+++ /dev/null
@@ -1,507 +0,0 @@
-node-xml2js
-===========
-
-Ever had the urge to parse XML? And wanted to access the data in some sane,
-easy way? Don't want to compile a C parser, for whatever reason? Then xml2js is
-what you're looking for!
-
-Description
-===========
-
-Simple XML to JavaScript object converter. It supports bi-directional conversion.
-Uses [sax-js](https://github.com/isaacs/sax-js/) and
-[xmlbuilder-js](https://github.com/oozcitak/xmlbuilder-js/).
-
-Note: If you're looking for a full DOM parser, you probably want
-[JSDom](https://github.com/tmpvar/jsdom).
-
-Installation
-============
-
-Simplest way to install `xml2js` is to use [npm](http://npmjs.org), just `npm
-install xml2js` which will download xml2js and all dependencies.
-
-xml2js is also available via [Bower](http://bower.io/), just `bower install
-xml2js` which will download xml2js and all dependencies.
-
-Usage
-=====
-
-No extensive tutorials required because you are a smart developer! The task of
-parsing XML should be an easy one, so let's make it so! Here's some examples.
-
-Shoot-and-forget usage
-----------------------
-
-You want to parse XML as simple and easy as possible? It's dangerous to go
-alone, take this:
-
-```javascript
-var parseString = require('xml2js').parseString;
-var xml = "Hello xml2js! "
-parseString(xml, function (err, result) {
- console.dir(result);
-});
-```
-
-Can't get easier than this, right? This works starting with `xml2js` 0.2.3.
-With CoffeeScript it looks like this:
-
-```coffeescript
-{parseString} = require 'xml2js'
-xml = "Hello xml2js! "
-parseString xml, (err, result) ->
- console.dir result
-```
-
-If you need some special options, fear not, `xml2js` supports a number of
-options (see below), you can specify these as second argument:
-
-```javascript
-parseString(xml, {trim: true}, function (err, result) {
-});
-```
-
-Simple as pie usage
--------------------
-
-That's right, if you have been using xml-simple or a home-grown
-wrapper, this was added in 0.1.11 just for you:
-
-```javascript
-var fs = require('fs'),
- xml2js = require('xml2js');
-
-var parser = new xml2js.Parser();
-fs.readFile(__dirname + '/foo.xml', function(err, data) {
- parser.parseString(data, function (err, result) {
- console.dir(result);
- console.log('Done');
- });
-});
-```
-
-Look ma, no event listeners!
-
-You can also use `xml2js` from
-[CoffeeScript](https://github.com/jashkenas/coffeescript), further reducing
-the clutter:
-
-```coffeescript
-fs = require 'fs',
-xml2js = require 'xml2js'
-
-parser = new xml2js.Parser()
-fs.readFile __dirname + '/foo.xml', (err, data) ->
- parser.parseString data, (err, result) ->
- console.dir result
- console.log 'Done.'
-```
-
-But what happens if you forget the `new` keyword to create a new `Parser`? In
-the middle of a nightly coding session, it might get lost, after all. Worry
-not, we got you covered! Starting with 0.2.8 you can also leave it out, in
-which case `xml2js` will helpfully add it for you, no bad surprises and
-inexplicable bugs!
-
-Promise usage
--------------
-
-```javascript
-var xml2js = require('xml2js');
-var xml = ' ';
-
-// With parser
-var parser = new xml2js.Parser(/* options */);
-parser.parseStringPromise(xml).then(function (result) {
- console.dir(result);
- console.log('Done');
-})
-.catch(function (err) {
- // Failed
-});
-
-// Without parser
-xml2js.parseStringPromise(xml /*, options */).then(function (result) {
- console.dir(result);
- console.log('Done');
-})
-.catch(function (err) {
- // Failed
-});
-```
-
-Parsing multiple files
-----------------------
-
-If you want to parse multiple files, you have multiple possibilities:
-
- * You can create one `xml2js.Parser` per file. That's the recommended one
- and is promised to always *just work*.
- * You can call `reset()` on your parser object.
- * You can hope everything goes well anyway. This behaviour is not
- guaranteed work always, if ever. Use option #1 if possible. Thanks!
-
-So you wanna some JSON?
------------------------
-
-Just wrap the `result` object in a call to `JSON.stringify` like this
-`JSON.stringify(result)`. You get a string containing the JSON representation
-of the parsed object that you can feed to JSON-hungry consumers.
-
-Displaying results
-------------------
-
-You might wonder why, using `console.dir` or `console.log` the output at some
-level is only `[Object]`. Don't worry, this is not because `xml2js` got lazy.
-That's because Node uses `util.inspect` to convert the object into strings and
-that function stops after `depth=2` which is a bit low for most XML.
-
-To display the whole deal, you can use `console.log(util.inspect(result, false,
-null))`, which displays the whole result.
-
-So much for that, but what if you use
-[eyes](https://github.com/cloudhead/eyes.js) for nice colored output and it
-truncates the output with `…`? Don't fear, there's also a solution for that,
-you just need to increase the `maxLength` limit by creating a custom inspector
-`var inspect = require('eyes').inspector({maxLength: false})` and then you can
-easily `inspect(result)`.
-
-XML builder usage
------------------
-
-Since 0.4.0, objects can be also be used to build XML:
-
-```javascript
-var xml2js = require('xml2js');
-
-var obj = {name: "Super", Surname: "Man", age: 23};
-
-var builder = new xml2js.Builder();
-var xml = builder.buildObject(obj);
-```
-will result in:
-
-```xml
-
-
- Super
- Man
- 23
-
-```
-
-At the moment, a one to one bi-directional conversion is guaranteed only for
-default configuration, except for `attrkey`, `charkey` and `explicitArray` options
-you can redefine to your taste. Writing CDATA is supported via setting the `cdata`
-option to `true`.
-
-To specify attributes:
-```javascript
-var xml2js = require('xml2js');
-
-var obj = {root: {$: {id: "my id"}, _: "my inner text"}};
-
-var builder = new xml2js.Builder();
-var xml = builder.buildObject(obj);
-```
-will result in:
-```xml
-
-my inner text
-```
-
-### Adding xmlns attributes
-
-You can generate XML that declares XML namespace prefix / URI pairs with xmlns attributes.
-
-Example declaring a default namespace on the root element:
-
-```javascript
-let obj = {
- Foo: {
- $: {
- "xmlns": "http://foo.com"
- }
- }
-};
-```
-Result of `buildObject(obj)`:
-```xml
-
-```
-Example declaring non-default namespaces on non-root elements:
-```javascript
-let obj = {
- 'foo:Foo': {
- $: {
- 'xmlns:foo': 'http://foo.com'
- },
- 'bar:Bar': {
- $: {
- 'xmlns:bar': 'http://bar.com'
- }
- }
- }
-}
-```
-Result of `buildObject(obj)`:
-```xml
-
-
-
-```
-
-
-Processing attribute, tag names and values
-------------------------------------------
-
-Since 0.4.1 you can optionally provide the parser with attribute name and tag name processors as well as element value processors (Since 0.4.14, you can also optionally provide the parser with attribute value processors):
-
-```javascript
-
-function nameToUpperCase(name){
- return name.toUpperCase();
-}
-
-//transform all attribute and tag names and values to uppercase
-parseString(xml, {
- tagNameProcessors: [nameToUpperCase],
- attrNameProcessors: [nameToUpperCase],
- valueProcessors: [nameToUpperCase],
- attrValueProcessors: [nameToUpperCase]},
- function (err, result) {
- // processed data
-});
-```
-
-The `tagNameProcessors` and `attrNameProcessors` options
-accept an `Array` of functions with the following signature:
-
-```javascript
-function (name){
- //do something with `name`
- return name
-}
-```
-
-The `attrValueProcessors` and `valueProcessors` options
-accept an `Array` of functions with the following signature:
-
-```javascript
-function (value, name) {
- //`name` will be the node name or attribute name
- //do something with `value`, (optionally) dependent on the node/attr name
- return value
-}
-```
-
-Some processors are provided out-of-the-box and can be found in `lib/processors.js`:
-
-- `normalize`: transforms the name to lowercase.
-(Automatically used when `options.normalize` is set to `true`)
-
-- `firstCharLowerCase`: transforms the first character to lower case.
-E.g. 'MyTagName' becomes 'myTagName'
-
-- `stripPrefix`: strips the xml namespace prefix. E.g ` ` will become 'Bar'.
-(N.B.: the `xmlns` prefix is NOT stripped.)
-
-- `parseNumbers`: parses integer-like strings as integers and float-like strings as floats
-E.g. "0" becomes 0 and "15.56" becomes 15.56
-
-- `parseBooleans`: parses boolean-like strings to booleans
-E.g. "true" becomes true and "False" becomes false
-
-Options
-=======
-
-Apart from the default settings, there are a number of options that can be
-specified for the parser. Options are specified by ``new Parser({optionName:
-value})``. Possible options are:
-
- * `attrkey` (default: `$`): Prefix that is used to access the attributes.
- Version 0.1 default was `@`.
- * `charkey` (default: `_`): Prefix that is used to access the character
- content. Version 0.1 default was `#`.
- * `explicitCharkey` (default: `false`) Determines whether or not to use
- a `charkey` prefix for elements with no attributes.
- * `trim` (default: `false`): Trim the whitespace at the beginning and end of
- text nodes.
- * `normalizeTags` (default: `false`): Normalize all tag names to lowercase.
- * `normalize` (default: `false`): Trim whitespaces inside text nodes.
- * `explicitRoot` (default: `true`): Set this if you want to get the root
- node in the resulting object.
- * `emptyTag` (default: `''`): what will the value of empty nodes be. In case
- you want to use an empty object as a default value, it is better to provide a factory
- function `() => ({})` instead. Without this function a plain object would
- become a shared reference across all occurrences with unwanted behavior.
- * `explicitArray` (default: `true`): Always put child nodes in an array if
- true; otherwise an array is created only if there is more than one.
- * `ignoreAttrs` (default: `false`): Ignore all XML attributes and only create
- text nodes.
- * `mergeAttrs` (default: `false`): Merge attributes and child elements as
- properties of the parent, instead of keying attributes off a child
- attribute object. This option is ignored if `ignoreAttrs` is `true`.
- * `validator` (default `null`): You can specify a callable that validates
- the resulting structure somehow, however you want. See unit tests
- for an example.
- * `xmlns` (default `false`): Give each element a field usually called '$ns'
- (the first character is the same as attrkey) that contains its local name
- and namespace URI.
- * `explicitChildren` (default `false`): Put child elements to separate
- property. Doesn't work with `mergeAttrs = true`. If element has no children
- then "children" won't be created. Added in 0.2.5.
- * `childkey` (default `$$`): Prefix that is used to access child elements if
- `explicitChildren` is set to `true`. Added in 0.2.5.
- * `preserveChildrenOrder` (default `false`): Modifies the behavior of
- `explicitChildren` so that the value of the "children" property becomes an
- ordered array. When this is `true`, every node will also get a `#name` field
- whose value will correspond to the XML nodeName, so that you may iterate
- the "children" array and still be able to determine node names. The named
- (and potentially unordered) properties are also retained in this
- configuration at the same level as the ordered "children" array. Added in
- 0.4.9.
- * `charsAsChildren` (default `false`): Determines whether chars should be
- considered children if `explicitChildren` is on. Added in 0.2.5.
- * `includeWhiteChars` (default `false`): Determines whether whitespace-only
- text nodes should be included. Added in 0.4.17.
- * `async` (default `false`): Should the callbacks be async? This *might* be
- an incompatible change if your code depends on sync execution of callbacks.
- Future versions of `xml2js` might change this default, so the recommendation
- is to not depend on sync execution anyway. Added in 0.2.6.
- * `strict` (default `true`): Set sax-js to strict or non-strict parsing mode.
- Defaults to `true` which is *highly* recommended, since parsing HTML which
- is not well-formed XML might yield just about anything. Added in 0.2.7.
- * `attrNameProcessors` (default: `null`): Allows the addition of attribute
- name processing functions. Accepts an `Array` of functions with following
- signature:
- ```javascript
- function (name){
- //do something with `name`
- return name
- }
- ```
- Added in 0.4.14
- * `attrValueProcessors` (default: `null`): Allows the addition of attribute
- value processing functions. Accepts an `Array` of functions with following
- signature:
- ```javascript
- function (value, name){
- //do something with `name`
- return name
- }
- ```
- Added in 0.4.1
- * `tagNameProcessors` (default: `null`): Allows the addition of tag name
- processing functions. Accepts an `Array` of functions with following
- signature:
- ```javascript
- function (name){
- //do something with `name`
- return name
- }
- ```
- Added in 0.4.1
- * `valueProcessors` (default: `null`): Allows the addition of element value
- processing functions. Accepts an `Array` of functions with following
- signature:
- ```javascript
- function (value, name){
- //do something with `name`
- return name
- }
- ```
- Added in 0.4.6
-
-Options for the `Builder` class
--------------------------------
-These options are specified by ``new Builder({optionName: value})``.
-Possible options are:
-
- * `attrkey` (default: `$`): Prefix that is used to access the attributes.
- Version 0.1 default was `@`.
- * `charkey` (default: `_`): Prefix that is used to access the character
- content. Version 0.1 default was `#`.
- * `rootName` (default `root` or the root key name): root element name to be used in case
- `explicitRoot` is `false` or to override the root element name.
- * `renderOpts` (default `{ 'pretty': true, 'indent': ' ', 'newline': '\n' }`):
- Rendering options for xmlbuilder-js.
- * pretty: prettify generated XML
- * indent: whitespace for indentation (only when pretty)
- * newline: newline char (only when pretty)
- * `xmldec` (default `{ 'version': '1.0', 'encoding': 'UTF-8', 'standalone': true }`:
- XML declaration attributes.
- * `xmldec.version` A version number string, e.g. 1.0
- * `xmldec.encoding` Encoding declaration, e.g. UTF-8
- * `xmldec.standalone` standalone document declaration: true or false
- * `doctype` (default `null`): optional DTD. Eg. `{'ext': 'hello.dtd'}`
- * `headless` (default: `false`): omit the XML header. Added in 0.4.3.
- * `allowSurrogateChars` (default: `false`): allows using characters from the Unicode
- surrogate blocks.
- * `cdata` (default: `false`): wrap text nodes in `` instead of
- escaping when necessary. Does not add `` if it is not required.
- Added in 0.4.5.
-
-`renderOpts`, `xmldec`,`doctype` and `headless` pass through to
-[xmlbuilder-js](https://github.com/oozcitak/xmlbuilder-js).
-
-Updating to new version
-=======================
-
-Version 0.2 changed the default parsing settings, but version 0.1.14 introduced
-the default settings for version 0.2, so these settings can be tried before the
-migration.
-
-```javascript
-var xml2js = require('xml2js');
-var parser = new xml2js.Parser(xml2js.defaults["0.2"]);
-```
-
-To get the 0.1 defaults in version 0.2 you can just use
-`xml2js.defaults["0.1"]` in the same place. This provides you with enough time
-to migrate to the saner way of parsing in `xml2js` 0.2. We try to make the
-migration as simple and gentle as possible, but some breakage cannot be
-avoided.
-
-So, what exactly did change and why? In 0.2 we changed some defaults to parse
-the XML in a more universal and sane way. So we disabled `normalize` and `trim`
-so `xml2js` does not cut out any text content. You can reenable this at will of
-course. A more important change is that we return the root tag in the resulting
-JavaScript structure via the `explicitRoot` setting, so you need to access the
-first element. This is useful for anybody who wants to know what the root node
-is and preserves more information. The last major change was to enable
-`explicitArray`, so everytime it is possible that one might embed more than one
-sub-tag into a tag, xml2js >= 0.2 returns an array even if the array just
-includes one element. This is useful when dealing with APIs that return
-variable amounts of subtags.
-
-Running tests, development
-==========================
-
-[![Build Status](https://travis-ci.org/Leonidas-from-XIV/node-xml2js.svg?branch=master)](https://travis-ci.org/Leonidas-from-XIV/node-xml2js)
-[![Coverage Status](https://coveralls.io/repos/Leonidas-from-XIV/node-xml2js/badge.svg?branch=)](https://coveralls.io/r/Leonidas-from-XIV/node-xml2js?branch=master)
-[![Dependency Status](https://david-dm.org/Leonidas-from-XIV/node-xml2js.svg)](https://david-dm.org/Leonidas-from-XIV/node-xml2js)
-
-The development requirements are handled by npm, you just need to install them.
-We also have a number of unit tests, they can be run using `npm test` directly
-from the project root. This runs zap to discover all the tests and execute
-them.
-
-If you like to contribute, keep in mind that `xml2js` is written in
-CoffeeScript, so don't develop on the JavaScript files that are checked into
-the repository for convenience reasons. Also, please write some unit test to
-check your behaviour and if it is some user-facing thing, add some
-documentation to this README, so people will know it exists. Thanks in advance!
-
-Getting support
-===============
-
-Please, if you have a problem with the library, first make sure you read this
-README. If you read this far, thanks, you're good. Then, please make sure your
-problem really is with `xml2js`. It is? Okay, then I'll look at it. Send me a
-mail and we can talk. Please don't open issues, as I don't think that is the
-proper forum for support problems. Some problems might as well really be bugs
-in `xml2js`, if so I'll let you know to open an issue instead :)
-
-But if you know you really found a bug, feel free to open an issue instead.
diff --git a/developer/src/common/web/utils/src/deps/xml2js/bom.js b/developer/src/common/web/utils/src/deps/xml2js/bom.js
deleted file mode 100644
index 0ad6e2a4a43..00000000000
--- a/developer/src/common/web/utils/src/deps/xml2js/bom.js
+++ /dev/null
@@ -1,8 +0,0 @@
-"use strict";
-export function stripBOM(str) {
- if (str[0] === '\uFEFF') {
- return str.substring(1);
- } else {
- return str;
- }
-};
diff --git a/developer/src/common/web/utils/src/deps/xml2js/builder.js b/developer/src/common/web/utils/src/deps/xml2js/builder.js
deleted file mode 100644
index 5dfca61c9f6..00000000000
--- a/developer/src/common/web/utils/src/deps/xml2js/builder.js
+++ /dev/null
@@ -1,118 +0,0 @@
-var escapeCDATA, requiresCDATA, wrapCDATA,
- hasProp = {}.hasOwnProperty;
-
-import * as builder from 'xmlbuilder';
-import { defaults } from './defaults.js';
-
-requiresCDATA = function(entry) {
- return typeof entry === "string" && (entry.indexOf('&') >= 0 || entry.indexOf('>') >= 0 || entry.indexOf('<') >= 0);
-};
-
-wrapCDATA = function(entry) {
- return "";
-};
-
-escapeCDATA = function(entry) {
- return entry.replace(']]>', ']]]]>');
-};
-
-export class Builder {
- constructor(opts) {
- var key, ref, value;
- this.options = {};
- ref = defaults["0.2"];
- for (key in ref) {
- if (!hasProp.call(ref, key)) continue;
- value = ref[key];
- this.options[key] = value;
- }
- for (key in opts) {
- if (!hasProp.call(opts, key)) continue;
- value = opts[key];
- this.options[key] = value;
- }
- }
-
- buildObject(rootObj) {
- var attrkey, charkey, render, rootElement, rootName;
- attrkey = this.options.attrkey;
- charkey = this.options.charkey;
- if ((Object.keys(rootObj).length === 1) && (this.options.rootName === defaults['0.2'].rootName)) {
- rootName = Object.keys(rootObj)[0];
- rootObj = rootObj[rootName];
- } else {
- rootName = this.options.rootName;
- }
- render = (function(_this) {
- return function(element, obj) {
- var attr, child, entry, index, key, value;
- if (typeof obj !== 'object') {
- if (_this.options.cdata && requiresCDATA(obj)) {
- element.raw(wrapCDATA(obj));
- } else {
- element.txt(obj);
- }
- } else if (Array.isArray(obj)) {
- for (index in obj) {
- if (!hasProp.call(obj, index)) continue;
- child = obj[index];
- for (key in child) {
- entry = child[key];
- element = render(element.ele(key), entry).up();
- }
- }
- } else {
- for (key in obj) {
- if (!hasProp.call(obj, key)) continue;
- child = obj[key];
- if (key === attrkey) {
- if (typeof child === "object") {
- for (attr in child) {
- value = child[attr];
- element = element.att(attr, value);
- }
- }
- } else if (key === charkey) {
- if (_this.options.cdata && requiresCDATA(child)) {
- element = element.raw(wrapCDATA(child));
- } else {
- element = element.txt(child);
- }
- } else if (Array.isArray(child)) {
- for (index in child) {
- if (!hasProp.call(child, index)) continue;
- entry = child[index];
- if (typeof entry === 'string') {
- if (_this.options.cdata && requiresCDATA(entry)) {
- element = element.ele(key).raw(wrapCDATA(entry)).up();
- } else {
- element = element.ele(key, entry).up();
- }
- } else {
- element = render(element.ele(key), entry).up();
- }
- }
- } else if (typeof child === "object") {
- element = render(element.ele(key), child).up();
- } else {
- if (typeof child === 'string' && _this.options.cdata && requiresCDATA(child)) {
- element = element.ele(key).raw(wrapCDATA(child)).up();
- } else {
- if (child == null) {
- child = '';
- }
- element = element.ele(key, child.toString()).up();
- }
- }
- }
- }
- return element;
- };
- })(this);
- rootElement = builder.create(rootName, this.options.xmldec, this.options.doctype, {
- headless: this.options.headless,
- allowSurrogateChars: this.options.allowSurrogateChars
- });
- return render(rootElement, rootObj).end(this.options.renderOpts);
- };
-}
diff --git a/developer/src/common/web/utils/src/deps/xml2js/defaults.js b/developer/src/common/web/utils/src/deps/xml2js/defaults.js
deleted file mode 100644
index d1009281792..00000000000
--- a/developer/src/common/web/utils/src/deps/xml2js/defaults.js
+++ /dev/null
@@ -1,69 +0,0 @@
-// Generated by CoffeeScript 1.12.7
-export const defaults = {
- "0.1": {
- explicitCharkey: false,
- trim: true,
- normalize: true,
- normalizeTags: false,
- attrkey: "@",
- charkey: "#",
- explicitArray: false,
- ignoreAttrs: false,
- mergeAttrs: false,
- explicitRoot: false,
- validator: null,
- xmlns: false,
- explicitChildren: false,
- childkey: '@@',
- charsAsChildren: false,
- includeWhiteChars: false,
- async: false,
- strict: true,
- attrNameProcessors: null,
- attrValueProcessors: null,
- tagNameProcessors: null,
- valueProcessors: null,
- emptyTag: ''
- },
- "0.2": {
- explicitCharkey: false,
- trim: false,
- normalize: false,
- normalizeTags: false,
- attrkey: "$",
- charkey: "_",
- explicitArray: true,
- ignoreAttrs: false,
- mergeAttrs: false,
- explicitRoot: true,
- validator: null,
- xmlns: false,
- explicitChildren: false,
- preserveChildrenOrder: false,
- childkey: '$$',
- charsAsChildren: false,
- includeWhiteChars: false,
- async: false,
- strict: true,
- attrNameProcessors: null,
- attrValueProcessors: null,
- tagNameProcessors: null,
- valueProcessors: null,
- rootName: 'root',
- xmldec: {
- 'version': '1.0',
- 'encoding': 'UTF-8',
- 'standalone': true
- },
- doctype: null,
- renderOpts: {
- 'pretty': true,
- 'indent': ' ',
- 'newline': '\n'
- },
- headless: false,
- chunkSize: 10000,
- emptyTag: '',
- cdata: false
- }
- };
diff --git a/developer/src/common/web/utils/src/deps/xml2js/parser.js b/developer/src/common/web/utils/src/deps/xml2js/parser.js
deleted file mode 100644
index 1c7f148d906..00000000000
--- a/developer/src/common/web/utils/src/deps/xml2js/parser.js
+++ /dev/null
@@ -1,381 +0,0 @@
- var isEmpty, processItem,
- bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
- extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
- hasProp = {}.hasOwnProperty;
-
-import sax from 'sax';
-import { EventEmitter } from 'eventemitter3';
-import * as bom from './bom.js';
-import * as processors from './processors.js';
-import { setImmediate } from 'timers';
-import { defaults } from './defaults.js';
-
- isEmpty = function(thing) {
- return typeof thing === "object" && (thing != null) && Object.keys(thing).length === 0;
- };
-
- processItem = function(processors, item, key) {
- var i, len, process;
- for (i = 0, len = processors.length; i < len; i++) {
- process = processors[i];
- item = process(item, key);
- }
- return item;
- };
-
-/** @type Class */
-export class Parser extends EventEmitter {
-// export const Parser = (function(superClass) {
- // extend(Parser, superClass);
-
- constructor(opts) {
- super();
- this.parseStringPromise = bind(this.parseStringPromise, this);
- this.parseString = bind(this.parseString, this);
- this.reset = bind(this.reset, this);
- this.assignOrPush = bind(this.assignOrPush, this);
- this.processAsync = bind(this.processAsync, this);
- var key, ref, value;
- if (!(this instanceof Parser)) {
- return new Parser(opts);
- }
- this.options = {};
- ref = defaults["0.2"];
- for (key in ref) {
- if (!hasProp.call(ref, key)) continue;
- value = ref[key];
- this.options[key] = value;
- }
- for (key in opts) {
- if (!hasProp.call(opts, key)) continue;
- value = opts[key];
- this.options[key] = value;
- }
- if (this.options.xmlns) {
- this.options.xmlnskey = this.options.attrkey + "ns";
- }
- if (this.options.normalizeTags) {
- if (!this.options.tagNameProcessors) {
- this.options.tagNameProcessors = [];
- }
- this.options.tagNameProcessors.unshift(processors.normalize);
- }
- this.reset();
- }
-
- processAsync() {
- var chunk, err;
- try {
- if (this.remaining.length <= this.options.chunkSize) {
- chunk = this.remaining;
- this.remaining = '';
- this.saxParser = this.saxParser.write(chunk);
- return this.saxParser.close();
- } else {
- chunk = this.remaining.substr(0, this.options.chunkSize);
- this.remaining = this.remaining.substr(this.options.chunkSize, this.remaining.length);
- this.saxParser = this.saxParser.write(chunk);
- return setImmediate(this.processAsync);
- }
- } catch (error1) {
- err = error1;
- if (!this.saxParser.errThrown) {
- this.saxParser.errThrown = true;
- return this.emit(err);
- }
- }
- };
-
- assignOrPush(obj, key, newValue) {
- if (!(key in obj)) {
- if (!this.options.explicitArray) {
- return obj[key] = newValue;
- } else {
- return obj[key] = [newValue];
- }
- } else {
- if (!(obj[key] instanceof Array)) {
- obj[key] = [obj[key]];
- }
- return obj[key].push(newValue);
- }
- };
-
- reset() {
- var attrkey, charkey, ontext, stack;
- this.removeAllListeners();
- this.saxParser = sax.parser(this.options.strict, {
- trim: false,
- normalize: false,
- xmlns: this.options.xmlns
- });
- this.saxParser.errThrown = false;
- this.saxParser.onerror = (function(_this) {
- return function(error) {
- _this.saxParser.resume();
- if (!_this.saxParser.errThrown) {
- _this.saxParser.errThrown = true;
- return _this.emit("error", error);
- }
- };
- })(this);
- this.saxParser.onend = (function(_this) {
- return function() {
- if (!_this.saxParser.ended) {
- _this.saxParser.ended = true;
- return _this.emit("end", _this.resultObject);
- }
- };
- })(this);
- this.saxParser.ended = false;
- this.EXPLICIT_CHARKEY = this.options.explicitCharkey;
- this.resultObject = null;
- stack = [];
- attrkey = this.options.attrkey;
- charkey = this.options.charkey;
- this.saxParser.onopentag = (function(_this) {
- return function(node) {
- var key, newValue, obj, processedKey, ref;
- obj = Object.create(null);
- obj[charkey] = "";
- if (!_this.options.ignoreAttrs) {
- ref = node.attributes;
- for (key in ref) {
- if (!hasProp.call(ref, key)) continue;
- if (!(attrkey in obj) && !_this.options.mergeAttrs) {
- obj[attrkey] = Object.create(null);
- }
- newValue = _this.options.attrValueProcessors ? processItem(_this.options.attrValueProcessors, node.attributes[key], key) : node.attributes[key];
- processedKey = _this.options.attrNameProcessors ? processItem(_this.options.attrNameProcessors, key) : key;
- if (_this.options.mergeAttrs) {
- _this.assignOrPush(obj, processedKey, newValue);
- } else {
- obj[attrkey][processedKey] = newValue;
- }
- }
- }
- obj["#name"] = _this.options.tagNameProcessors ? processItem(_this.options.tagNameProcessors, node.name) : node.name;
- if (_this.options.xmlns) {
- obj[_this.options.xmlnskey] = {
- uri: node.uri,
- local: node.local
- };
- }
- return stack.push(obj);
- };
- })(this);
- this.saxParser.onclosetag = (function(_this) {
- return function() {
- var cdata, emptyStr, key, node, nodeName, obj, objClone, old, s, xpath;
- obj = stack.pop();
- nodeName = obj["#name"];
- if (!_this.options.explicitChildren || !_this.options.preserveChildrenOrder) {
- delete obj["#name"];
- }
- if (obj.cdata === true) {
- cdata = obj.cdata;
- delete obj.cdata;
- }
- s = stack[stack.length - 1];
- if (obj[charkey].match(/^\s*$/) && !cdata && !_this.options.includeWhiteChars) {
- emptyStr = obj[charkey];
- delete obj[charkey];
- } else {
- if (_this.options.trim) {
- obj[charkey] = obj[charkey].trim();
- }
- if (_this.options.normalize) {
- obj[charkey] = obj[charkey].replace(/\s{2,}/g, " ").trim();
- }
- obj[charkey] = _this.options.valueProcessors ? processItem(_this.options.valueProcessors, obj[charkey], nodeName) : obj[charkey];
- if (Object.keys(obj).length === 1 && charkey in obj && !_this.EXPLICIT_CHARKEY) {
- obj = obj[charkey];
- }
- }
- if (isEmpty(obj)) {
- if (typeof _this.options.emptyTag === 'function') {
- obj = _this.options.emptyTag();
- } else {
- obj = _this.options.emptyTag !== '' ? _this.options.emptyTag : emptyStr;
- }
- }
- if (_this.options.validator != null) {
- xpath = "/" + ((function() {
- var i, len, results;
- results = [];
- for (i = 0, len = stack.length; i < len; i++) {
- node = stack[i];
- results.push(node["#name"]);
- }
- return results;
- })()).concat(nodeName).join("/");
- (function() {
- var err;
- try {
- return obj = _this.options.validator(xpath, s && s[nodeName], obj);
- } catch (error1) {
- err = error1;
- return _this.emit("error", err);
- }
- })();
- }
- if (_this.options.explicitChildren && !_this.options.mergeAttrs && typeof obj === 'object') {
- if (!_this.options.preserveChildrenOrder) {
- node = Object.create(null);
- if (_this.options.attrkey in obj) {
- node[_this.options.attrkey] = obj[_this.options.attrkey];
- delete obj[_this.options.attrkey];
- }
- if (!_this.options.charsAsChildren && _this.options.charkey in obj) {
- node[_this.options.charkey] = obj[_this.options.charkey];
- delete obj[_this.options.charkey];
- }
- if (Object.getOwnPropertyNames(obj).length > 0) {
- node[_this.options.childkey] = obj;
- }
- obj = node;
- } else if (s) {
- s[_this.options.childkey] = s[_this.options.childkey] || [];
- objClone = Object.create(null);
- for (key in obj) {
- if (!hasProp.call(obj, key)) continue;
- objClone[key] = obj[key];
- }
- s[_this.options.childkey].push(objClone);
- delete obj["#name"];
- if (Object.keys(obj).length === 1 && charkey in obj && !_this.EXPLICIT_CHARKEY) {
- obj = obj[charkey];
- }
- }
- }
- if (stack.length > 0) {
- return _this.assignOrPush(s, nodeName, obj);
- } else {
- if (_this.options.explicitRoot) {
- old = obj;
- obj = Object.create(null);
- obj[nodeName] = old;
- }
- _this.resultObject = obj;
- _this.saxParser.ended = true;
- return _this.emit("end", _this.resultObject);
- }
- };
- })(this);
- ontext = (function(_this) {
- return function(text) {
- var charChild, s;
- s = stack[stack.length - 1];
- if (s) {
- s[charkey] += text;
- if (_this.options.explicitChildren && _this.options.preserveChildrenOrder && _this.options.charsAsChildren && (_this.options.includeWhiteChars || text.replace(/\\n/g, '').trim() !== '')) {
- s[_this.options.childkey] = s[_this.options.childkey] || [];
- charChild = {
- '#name': '__text__'
- };
- charChild[charkey] = text;
- if (_this.options.normalize) {
- charChild[charkey] = charChild[charkey].replace(/\s{2,}/g, " ").trim();
- }
- s[_this.options.childkey].push(charChild);
- }
- return s;
- }
- };
- })(this);
- this.saxParser.ontext = ontext;
- return this.saxParser.oncdata = (function(_this) {
- return function(text) {
- var s;
- s = ontext(text);
- if (s) {
- return s.cdata = true;
- }
- };
- })(this);
- };
-
- parseString(str, cb) {
- var err;
- if ((cb != null) && typeof cb === "function") {
- this.on("end", function(result) {
- this.reset();
- return cb(null, result);
- });
- this.on("error", function(err) {
- this.reset();
- return cb(err);
- });
- }
- try {
- str = str.toString();
- if (str.trim() === '') {
- this.emit("end", null);
- return true;
- }
- str = bom.stripBOM(str);
- if (this.options.async) {
- this.remaining = str;
- setImmediate(this.processAsync);
- return this.saxParser;
- }
- return this.saxParser.write(str).close();
- } catch (error1) {
- err = error1;
- if (!(this.saxParser.errThrown || this.saxParser.ended)) {
- if(this.listenerCount('error') > 0) {
- this.emit('error', err);
- } else {
- throw err;
- }
- return this.saxParser.errThrown = true;
- } else if (this.saxParser.ended) {
- throw err;
- }
- }
- };
-
- parseStringPromise(str) {
- return new Promise((function(_this) {
- return function(resolve, reject) {
- return _this.parseString(str, function(err, value) {
- if (err) {
- return reject(err);
- } else {
- return resolve(value);
- }
- });
- };
- })(this));
- };
-
- }
-
- export const parseString = function(str, a, b) {
- var cb, options, parser;
- if (b != null) {
- if (typeof b === 'function') {
- cb = b;
- }
- if (typeof a === 'object') {
- options = a;
- }
- } else {
- if (typeof a === 'function') {
- cb = a;
- }
- options = {};
- }
- parser = new Parser(options);
- return parser.parseString(str, cb);
- };
-
- export const parseStringPromise = function(str, a) {
- var options, parser;
- if (typeof a === 'object') {
- options = a;
- }
- parser = new Parser(options);
- return parser.parseStringPromise(str);
- };
-
diff --git a/developer/src/common/web/utils/src/deps/xml2js/processors.js b/developer/src/common/web/utils/src/deps/xml2js/processors.js
deleted file mode 100644
index 2a6849088c1..00000000000
--- a/developer/src/common/web/utils/src/deps/xml2js/processors.js
+++ /dev/null
@@ -1,31 +0,0 @@
- "use strict";
- var prefixMatch;
-
- prefixMatch = new RegExp(/(?!xmlns)^.*:/);
-
- export const normalize = function(str) {
- return str.toLowerCase();
- };
-
- export const firstCharLowerCase = function(str) {
- return str.charAt(0).toLowerCase() + str.slice(1);
- };
-
- export const stripPrefix = function(str) {
- return str.replace(prefixMatch, '');
- };
-
- export const parseNumbers = function(str) {
- if (!isNaN(str)) {
- str = str % 1 === 0 ? parseInt(str, 10) : parseFloat(str);
- }
- return str;
- };
-
- export const parseBooleans = function(str) {
- if (/^(?:true|false)$/i.test(str)) {
- str = str.toLowerCase() === 'true';
- }
- return str;
- };
-
diff --git a/developer/src/common/web/utils/src/deps/xml2js/xml2js.js b/developer/src/common/web/utils/src/deps/xml2js/xml2js.js
deleted file mode 100644
index 8ecbe3bcf2f..00000000000
--- a/developer/src/common/web/utils/src/deps/xml2js/xml2js.js
+++ /dev/null
@@ -1,27 +0,0 @@
-var
- extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
- hasProp = {}.hasOwnProperty;
-
- // import { defaults } from './defaults.js';
- import * as builder from './builder.js';
- import * as parser from './parser.js';
- import * as processors from './processors.js';
-
- // export const defaults = defaults.defaults;
- // export const processors = processors;
-
- /** @type Class */
- export class ValidationError extends Error {
- constructor(message) {
- super(message);
- this.message = message;
- }
- };
-
- export const Parser = parser.Parser;
-
- export const Builder = builder.Builder;
-
- export const parseString = parser.parseString;
-
- export const parseStringPromise = parser.parseStringPromise;
diff --git a/developer/src/common/web/utils/src/index.ts b/developer/src/common/web/utils/src/index.ts
index 0e462c7c056..21b59cc749f 100644
--- a/developer/src/common/web/utils/src/index.ts
+++ b/developer/src/common/web/utils/src/index.ts
@@ -42,6 +42,6 @@ export { defaultCompilerOptions, CompilerBaseOptions, CompilerCallbacks, Compile
} from './compiler-interfaces.js';
-export { CommonTypesMessages } from './common-events.js';
+export { CommonTypesMessages } from './common-messages.js';
-export * as xml2js from './deps/xml2js/xml2js.js';
+export { KeymanXMLType, KeymanXMLWriter, KeymanXMLReader } from './xml-utils.js';
diff --git a/developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts b/developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts
index d7768987531..f86879b0068 100644
--- a/developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts
+++ b/developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts
@@ -1,4 +1,4 @@
-import { xml2js } from '../../index.js';
+import { KeymanXMLReader } from '../../index.js';
import { KPJFile, KPJFileProject } from './kpj-file.js';
import { util } from '@keymanapp/common-types';
import { KeymanDeveloperProject, KeymanDeveloperProjectFile10, KeymanDeveloperProjectType } from './keyman-developer-project.js';
@@ -13,20 +13,9 @@ export class KPJFileReader {
public read(file: Uint8Array): KPJFile {
let data: KPJFile;
- const parser = new xml2js.Parser({
- explicitArray: false,
- mergeAttrs: false,
- includeWhiteChars: false,
- normalize: false,
- emptyTag: ''
- });
+ data = new KeymanXMLReader('kpj')
+ .parse(file.toString());
- parser.parseString(file, (e: unknown, r: unknown) => {
- if(e) {
- throw e;
- }
- data = r as KPJFile;
- });
data = this.boxArrays(data);
if(data.KeymanDeveloperProject?.Files?.File?.length) {
for(const file of data.KeymanDeveloperProject?.Files?.File) {
@@ -126,4 +115,4 @@ export class KPJFileReader {
util.boxXmlArray(source.KeymanDeveloperProject.Files, 'File');
return source;
}
-}
\ No newline at end of file
+}
diff --git a/developer/src/common/web/utils/src/types/kvks/kvks-file-reader.ts b/developer/src/common/web/utils/src/types/kvks/kvks-file-reader.ts
index 6b2ad8e0a98..9f2ca18f887 100644
--- a/developer/src/common/web/utils/src/types/kvks/kvks-file-reader.ts
+++ b/developer/src/common/web/utils/src/types/kvks/kvks-file-reader.ts
@@ -1,5 +1,5 @@
import { SchemaValidators as SV, KvkFile, util, Constants } from '@keymanapp/common-types';
-import { xml2js } from '../../index.js'
+import { KeymanXMLReader } from '../../index.js'
import KVKSourceFile from './kvks-file.js';
const SchemaValidators = SV.default;
import boxXmlArray = util.boxXmlArray;
@@ -20,31 +20,15 @@ export default class KVKSFileReader {
public read(file: Uint8Array): KVKSourceFile {
let source: KVKSourceFile;
- const parser = new xml2js.Parser({
- explicitArray: false,
- mergeAttrs: false,
- includeWhiteChars: true,
- normalize: false,
- emptyTag: {} as any
- // Why "as any"? xml2js is broken:
- // https://github.com/Leonidas-from-XIV/node-xml2js/issues/648 means
- // that an old version of `emptyTag` is used which doesn't support
- // functions, but DefinitelyTyped is requiring use of function or a
- // string. See also notes at
- // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/59259#issuecomment-1254405470
- // An alternative fix would be to pull xml2js directly from github
- // rather than using the version tagged on npmjs.com.
- });
-
- parser.parseString(file, (e: unknown, r: unknown) => {
- if(e) {
- if(file.byteLength > 4 && file.subarray(0,3).every((v,i) => v == KVK_HEADER_IDENTIFIER_BYTES[i])) {
- throw new Error('File appears to be a binary .kvk file', {cause: e});
- }
- throw e;
- };
- source = r as KVKSourceFile;
- });
+ try {
+ source = new KeymanXMLReader('kvks')
+ .parse(file.toString()) as KVKSourceFile;
+ } catch(e) {
+ if(file.byteLength > 4 && file.subarray(0,3).every((v,i) => v == KVK_HEADER_IDENTIFIER_BYTES[i])) {
+ throw new Error('File appears to be a binary .kvk file', {cause: e});
+ }
+ throw e;
+ }
if(source) {
source = this.boxArrays(source);
this.cleanupFlags(source);
@@ -197,4 +181,4 @@ export default class KVKSFileReader {
}
return 0;
}
-}
\ No newline at end of file
+}
diff --git a/developer/src/common/web/utils/src/types/kvks/kvks-file-writer.ts b/developer/src/common/web/utils/src/types/kvks/kvks-file-writer.ts
index dd4962a8fad..70534ff4408 100644
--- a/developer/src/common/web/utils/src/types/kvks/kvks-file-writer.ts
+++ b/developer/src/common/web/utils/src/types/kvks/kvks-file-writer.ts
@@ -1,6 +1,6 @@
import { VisualKeyboard as VK, Constants } from '@keymanapp/common-types';
import KVKSourceFile, { KVKSEncoding, KVKSFlags, KVKSKey, KVKSLayer } from './kvks-file.js';
-import { xml2js } from '../../index.js';
+import { KeymanXMLWriter } from '../../index.js';
import USVirtualKeyCodes = Constants.USVirtualKeyCodes;
import VisualKeyboard = VK.VisualKeyboard;
@@ -11,18 +11,6 @@ import VisualKeyboardShiftState = VK.VisualKeyboardShiftState;
export default class KVKSFileWriter {
public write(vk: VisualKeyboard): string {
-
- const builder = new xml2js.Builder({
- allowSurrogateChars: true,
- attrkey: '$',
- charkey: '_',
- xmldec: {
- version: '1.0',
- encoding: 'UTF-8',
- standalone: true
- }
- })
-
const flags: KVKSFlags = {};
if(vk.header.flags & VisualKeyboardHeaderFlags.kvkhDisplayUnderlying) {
flags.displayunderlying = '';
@@ -37,8 +25,6 @@ export default class KVKSFileWriter {
flags.useunderlying = '';
}
-
-
const kvks: KVKSourceFile = {
visualkeyboard: {
header: {
@@ -105,7 +91,7 @@ export default class KVKSFileWriter {
l.key.push(k);
}
- const result = builder.buildObject(kvks);
+ const result = new KeymanXMLWriter('kvks').write(kvks);
return result; //Uint8Array.from(result);
}
@@ -124,4 +110,4 @@ export default class KVKSFileWriter {
}
return '';
}
-}
\ No newline at end of file
+}
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 ddeaf719abd..17ade173931 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
@@ -1,11 +1,15 @@
+/*
+ * Keyman is copyright (C) SIL International. MIT License.
+ *
+ * Reads a LDML XML keyboard file into JS object tree and resolves imports
+ */
import { SchemaValidators, util } from '@keymanapp/common-types';
-import { xml2js } from '../../index.js';
-import { CommonTypesMessages } from '../../common-events.js';
+import { CommonTypesMessages } from '../../common-messages.js';
import { CompilerCallbacks } from '../../compiler-interfaces.js';
import { LDMLKeyboardXMLSourceFile, LKImport, ImportStatus } from './ldml-keyboard-xml.js';
import { constants } from '@keymanapp/ldml-keyboard-constants';
import { LDMLKeyboardTestDataXMLSourceFile, LKTTest, LKTTests } from './ldml-keyboard-testdata-xml.js';
-
+import { KeymanXMLReader } from '@keymanapp/developer-utils';
import boxXmlArray = util.boxXmlArray;
interface NameAndProps {
@@ -93,6 +97,14 @@ export class LDMLKeyboardXMLSourceFileReader {
if(source?.keyboard3?.transforms) {
for(const transforms of source.keyboard3.transforms) {
boxXmlArray(transforms, 'transformGroup');
+ // need to see if there's an empty ('') element.
+ // the schema allows an empty object, but the spec doesn't.
+ for (let i=0; i {
- let a: LDMLKeyboardXMLSourceFile;
- const parser = new xml2js.Parser({
- explicitArray: false,
- mergeAttrs: true,
- includeWhiteChars: false,
- emptyTag: {} as any
- // Why "as any"? xml2js is broken:
- // https://github.com/Leonidas-from-XIV/node-xml2js/issues/648 means
- // that an old version of `emptyTag` is used which doesn't support
- // functions, but DefinitelyTyped is requiring use of function or a
- // string. See also notes at
- // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/59259#issuecomment-1254405470
- // An alternative fix would be to pull xml2js directly from github
- // rather than using the version tagged on npmjs.com.
- });
- parser.parseString(file, (e: unknown, r: unknown) => { a = r as LDMLKeyboardXMLSourceFile }); // TODO-LDML: isn't 'e' the error?
- return a;
- })();
+ const data = new TextDecoder().decode(file);
+ const source = new KeymanXMLReader('keyboard3')
+ .parse(data) as LDMLKeyboardXMLSourceFile;
return source;
}
@@ -279,38 +281,28 @@ export class LDMLKeyboardXMLSourceFileReader {
*/
public load(file: Uint8Array): LDMLKeyboardXMLSourceFile | null {
if (!file) {
+ throw new Error(`file parameter must not be null`);
+ }
+
+ let source: LDMLKeyboardXMLSourceFile = null;
+ try {
+ source = this.loadUnboxed(file);
+ } catch(e) {
+ this.callbacks.reportMessage(CommonTypesMessages.Error_InvalidXml({e}));
return null;
}
- const source = this.loadUnboxed(file);
- if(this.boxArrays(source)) {
+
+ if (this.boxArrays(source)) {
return source;
- } else {
- return null;
}
+
+ // boxArrays ... resolveImports has already logged a message
+ return null;
}
loadTestDataUnboxed(file: Uint8Array): any {
- const source = (() => {
- let a: any;
- const parser = new xml2js.Parser({
- // explicitArray: false,
- preserveChildrenOrder:true, // needed for test data
- explicitChildren: true, // needed for test data
- // mergeAttrs: true,
- // includeWhiteChars: false,
- // emptyTag: {} as any
- // Why "as any"? xml2js is broken:
- // https://github.com/Leonidas-from-XIV/node-xml2js/issues/648 means
- // that an old version of `emptyTag` is used which doesn't support
- // functions, but DefinitelyTyped is requiring use of function or a
- // string. See also notes at
- // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/59259#issuecomment-1254405470
- // An alternative fix would be to pull xml2js directly from github
- // rather than using the version tagged on npmjs.com.
- });
- parser.parseString(file, (e: unknown, r: unknown) => { a = r as any }); // TODO-LDML: isn't 'e' the error?
- return a; // Why 'any'? Because we need to box up the $'s into proper properties.
- })();
+ const source = new KeymanXMLReader('keyboardTest3')
+ .parse(file.toString()) as any;
return source;
}
diff --git a/developer/src/common/web/utils/src/xml-utils.ts b/developer/src/common/web/utils/src/xml-utils.ts
new file mode 100644
index 00000000000..999da59fde3
--- /dev/null
+++ b/developer/src/common/web/utils/src/xml-utils.ts
@@ -0,0 +1,310 @@
+/*
+ * Keyman is copyright (C) SIL Global. MIT License.
+ *
+ * Created by srl on 2024-09-27
+ *
+ * Abstraction for XML reading and writing
+ */
+
+import { XMLParser, XMLBuilder } from 'fast-xml-parser';
+
+export type KeymanXMLType =
+ 'keyboard3' // LDML
+ | 'keyboardTest3' // LDML
+ | 'kps' //
+ | 'kvks' //
+ | 'kpj' //
+ ;
+
+/** Bag of options, maximally one for each KeymanXMLType */
+type KeymanXMLOptionsBag = {
+ [key in KeymanXMLType]?: any
+};
+
+/** map of options for the XML parser */
+const PARSER_OPTIONS: KeymanXMLOptionsBag = {
+ 'keyboard3': {
+ ignoreAttributes: false, // We'd like attributes, please
+ attributeNamePrefix: '@__', // We'll use this to convert attributes to strings and subobjects to arrays, when empty.
+ trimValues: false, // preserve spaces, but:
+ htmlEntities: true,
+ tagValueProcessor: (tagName: string, tagValue: string /*, jPath, hasAttributes, isLeafNode*/) => {
+ // since trimValues: false, we need to zap any element values that would be trimmed.
+ // currently, the LDML spec doesn't have any element values, but this
+ // future-proofs us a little in that element values are allowed, just trimmed.
+ // if we do need elements in the future, we'd check the preserve-space attribute here.
+ return tagValue?.trim();
+ },
+ },
+ 'keyboardTest3': {
+ ignorePiTags: true,
+ htmlEntities: true,
+ ignoreAttributes: false, // We'd like attributes, please
+ attributeNamePrefix: '', // avoid @_
+ preserveOrder: true, // Gives us a 'special' format
+ },
+ 'kps': {
+ ignorePiTags: true,
+ ignoreAttributes: false,
+ htmlEntities: true,
+ attributeNamePrefix: '$', // causes remapping into $: { … } objects
+ textNodeName: '_',
+ numberParseOptions: {
+ skipLike: /(?:)/, // parse numbers as strings
+ hex: null,
+ leadingZeros: null,
+ eNotation: null,
+ },
+ },
+ 'kpj': {
+ ignorePiTags: true,
+ textNodeName: '_',
+ htmlEntities: true,
+ ignoreAttributes: false, // We'd like attributes, please
+ attributeNamePrefix: '', // to avoid '@_' prefixes
+ numberParseOptions: {
+ skipLike: /(?:)/, // parse numbers as strings
+ hex: null,
+ leadingZeros: null,
+ eNotation: null,
+ },
+ },
+ 'kvks': {
+ ignorePiTags: true,
+ textNodeName: '_',
+ htmlEntities: true,
+ ignoreAttributes: false, // We'd like attributes, please
+ attributeNamePrefix: '$', // causes remapping into $: { … } objects
+ numberParseOptions: {
+ skipLike: /(?:)/, // parse numbers as strings
+ hex: null,
+ leadingZeros: null,
+ eNotation: null,
+ },
+ trimValues: false, // preserve spaces, but:
+ tagValueProcessor: (tagName: string, tagValue: string, jPath: string, hasAttributes: string, isLeafNode: boolean) : string | undefined => {
+ if (!isLeafNode) {
+ return tagValue?.trim(); // trimmed value
+ } else {
+ return null; // no change to leaf nodes
+ }
+ },
+ },
+};
+
+const GENERATOR_OPTIONS: KeymanXMLOptionsBag = {
+ kvks: {
+ attributeNamePrefix: '$',
+ ignoreAttributes: false,
+ format: true,
+ textNodeName: '_',
+ suppressEmptyNode: true,
+ },
+};
+
+/** wrapper for XML parsing support */
+export class KeymanXMLReader {
+ public constructor(public type: KeymanXMLType) {
+ }
+
+ /** move `{ $abc: 4 }` into `{ $: { abc: 4 } }` */
+ private static fixupDollarAttributes(data: any) : any {
+ if (typeof data === 'object') {
+ if (Array.isArray(data)) {
+ return data.map(v => KeymanXMLReader.fixupDollarAttributes(v));
+ }
+ // object
+ const e : any = [];
+ const attrs : any = [];
+ Object.entries(data).forEach(([k, v]) => {
+ if (k[0] === '$') {
+ k = k.slice(1);
+ attrs.push([k, KeymanXMLReader.fixupDollarAttributes(v)]);
+ } else {
+ e.push([k, KeymanXMLReader.fixupDollarAttributes(v)]);
+ }
+ });
+ if (attrs.length) {
+ e.push(['$', Object.fromEntries(attrs)]);
+ }
+ return Object.fromEntries(e);
+ } else {
+ return data;
+ }
+ }
+
+ /**
+ * Requires attribute prefix @__ (double underscore)
+ * For attributes, just remove @__ and continue.
+ * For objects, replace any empty string "" with an empty object {} */
+ private static fixupEmptyStringToEmptyObject(data: any) : any {
+ if (typeof data === 'object') {
+ // For arrays of objects, we map "" to {}
+ // "" means an empty object
+ if (Array.isArray(data)) {
+ return data.map(v => {
+ if (v === '') {
+ return {};
+ } else {
+ return KeymanXMLReader.fixupEmptyStringToEmptyObject(v);
+ }
+ });
+ }
+ // otherwise: remove @__ for attributes, remap objects
+ const e: any = [];
+ Object.entries(data).forEach(([k, v]) => {
+ if (k.startsWith('@__')) {
+ e.push([k.substring(3), KeymanXMLReader.fixupEmptyStringToEmptyObject(v)]);
+ } else {
+ if (v === '') {
+ e.push([k, {}]);
+ } else {
+ e.push([k, KeymanXMLReader.fixupEmptyStringToEmptyObject(v)]);
+ }
+ }
+ });
+ return Object.fromEntries(e);
+ } else {
+ return data;
+ }
+ }
+
+ /**
+ * Replace:
+ * ```json
+ * [ { "info": [], ":@": { "abc": "def" } }]
+ * ```
+ * with:
+ * ```json
+ * [{"$": { "abc": "def" }, "#name": "info" }]
+ * ```
+ * see https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/docs/v4/2.XMLparseOptions.md#preserveorder
+ * @param data input data
+ */
+ private static fixupPreserveOrder(data: any): any {
+
+ // we need to extract the root name specially
+ if (!Array.isArray(data)) {
+ throw Error(`Internal Error: XML parser preserveOrder did not yield an array.`);
+ }
+ if (data.length !== 1) {
+ // we ignore comments, so should only have one element
+ throw Error(`Internal Error: XML parser preserveOrder did not yield an array of size 1.`);
+ }
+ // the root element is special, we copy it into a property
+ const rootElement = KeymanXMLReader.fixupPreserveOrderObject(data[0]);
+ const rootElementName = rootElement['#name'];
+ const out: any = {};
+ out[rootElementName] = rootElement;
+ return out;
+ }
+
+ /** takes an 'object' with a property `:@` containing attrs, and one other property with the object name */
+ private static fixupPreserveOrderObject(data: any): any {
+ const attrs = data[':@'];
+ const mainEntry : any = Object.entries(data).filter(([k,v]) => k !== ':@');
+ const [elementName, subItems] = mainEntry[0];
+ const out : any = {};
+ if ( attrs ) {
+ out['$'] = attrs;
+ }
+ if (!elementName) {
+ throw Error(`could not find elementName in ${JSON.stringify(mainEntry[0])}`);
+ }
+ out['#name'] = elementName;
+ if (subItems && subItems.length) {
+ out['$$'] = subItems.map((subObject: any) => KeymanXMLReader.fixupPreserveOrderObject(subObject));
+ // xml2js duplicated data here, including elements in their 'non-preserved-order' form.
+ // we don't read this data, but we're maintaining compatibility here with the read format.
+ // example: emit: […], keystroke:[…]
+ for (const o of out['$$']) {
+ const subElementName = o['#name'];
+ const nonPreservedElements = out[subElementName] = out[subElementName] ?? [];
+ const oWithoutName = {...o};
+ delete oWithoutName['#name']; // #name is only there in the preserved-order form.
+ nonPreservedElements.push(oWithoutName);
+ }
+ }
+ return out;
+ }
+
+ public parse(data: string): any {
+ const parser = this.parser();
+ let result = parser.parse(data, true);
+ if (PARSER_OPTIONS[this.type].attributeNamePrefix === '$') {
+ result = KeymanXMLReader.fixupDollarAttributes(result);
+ } else if (PARSER_OPTIONS[this.type].attributeNamePrefix === '@__') {
+ result = KeymanXMLReader.fixupEmptyStringToEmptyObject(result);
+ } else if (PARSER_OPTIONS[this.type].preserveOrder) {
+ result = KeymanXMLReader.fixupPreserveOrder(result);
+ }
+ delete result['?xml'];
+ return result;
+ }
+
+ public parser() {
+ let options = PARSER_OPTIONS[this.type];
+ if (!options) {
+ /* c8 ignore next 1 */
+ throw Error(`Internal error: unhandled XML type ${this.type}`);
+ }
+ options = Object.assign({}, options); // TODO: xml2js likes to mutate the options here. Shallow clone the object.
+ if (options.emptyTag) {
+ options.emptyTag = {}; // TODO: xml2js likes to mutate the options here. Reset it.
+ }
+ return new XMLParser(options);
+ }
+}
+
+/**
+ * Fixed prologue for writing XML
+ */
+const PROLOGUE = { '?xml': { '$version': '1.0', '$encoding': 'utf-8' } };
+
+/** wrapper for XML generation support */
+export class KeymanXMLWriter {
+
+ private static fixDataForWrite(data: any) : any {
+ if(typeof data === 'object') {
+ if (Array.isArray(data)) {
+ // just fixup each item of the array
+ return data.map(d => KeymanXMLWriter.fixDataForWrite(d));
+ }
+ // else object
+ const e : any = [];
+ Object.entries(data).forEach(([k,v]) => {
+ if (k === '$') {
+ /* convert $: { a: 1, b: 2 } to { $a: 1, $b: 2} */
+ Object.entries(v).forEach(([k,v]) => {
+ e.push([`\$${k}`, KeymanXMLWriter.fixDataForWrite(v)]);
+ });
+ } else {
+ e.push([k, KeymanXMLWriter.fixDataForWrite(v)]);
+ }
+ });
+ // reconstitute with $ elements fixed
+ return Object.fromEntries(e);
+ } else {
+ return data; // string or something else
+ }
+ }
+
+ write(data: any): string {
+ const builder = this.builder();
+ data = KeymanXMLWriter.fixDataForWrite(data);
+ return builder.build({ ...PROLOGUE, ...data });
+ }
+
+ constructor(public type: KeymanXMLType) {
+ }
+
+ public builder() {
+ const options = GENERATOR_OPTIONS[this.type];
+ if (!options) {
+ /* c8 ignore next 1 */
+ throw Error(`Internal error: unhandled XML type ${this.type}`);
+ }
+ return new XMLBuilder(Object.assign({}, options)); // Shallow clone in case the options are mutated.
+ }
+}
+
diff --git a/developer/src/common/web/utils/test/fixtures/xml/disp_maximal.xml b/developer/src/common/web/utils/test/fixtures/xml/disp_maximal.xml
new file mode 100644
index 00000000000..aeab3a71a5c
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/disp_maximal.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/developer/src/common/web/utils/test/fixtures/xml/disp_maximal.xml.json b/developer/src/common/web/utils/test/fixtures/xml/disp_maximal.xml.json
new file mode 100644
index 00000000000..55ea878c833
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/disp_maximal.xml.json
@@ -0,0 +1,35 @@
+{
+ "keyboard3": {
+ "xmlns": "https://schemas.unicode.org/cldr/45/keyboard3",
+ "locale": "mt",
+ "conformsTo": "45",
+ "info": {
+ "name": "disp-maximal"
+ },
+ "displays": {
+ "display": [
+ {
+ "keyId": "g",
+ "display": "(g)"
+ },
+ {
+ "output": "f",
+ "display": "(f)"
+ },
+ {
+ "output": "${eee}",
+ "display": "(${eee})"
+ }
+ ],
+ "displayOptions": {
+ "baseCharacter": "x"
+ }
+ },
+ "variables": {
+ "string": {
+ "id": "eee",
+ "value": "e"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/developer/src/common/web/utils/test/fixtures/xml/error_invalid_package_file.kps b/developer/src/common/web/utils/test/fixtures/xml/error_invalid_package_file.kps
new file mode 100644
index 00000000000..e18eaa26a06
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/error_invalid_package_file.kps
@@ -0,0 +1,32 @@
+
+
+
+ 15.0.266.0
+ 7.0
+
+
+ SENĆOŦEN (Saanich Dialect) Keyboard
+
+ © 2019 National Research Council Canada & this test
+ Eddie Antonio Santos
+ 1.0
+
+
+
+ basic.kmx
+ Keyboard Basic
+ 0
+ .kmx
+
+
+
+
+ Basic
+ basic
+ 1.0
+
+ Khmer
+
+
+
+
diff --git a/developer/src/common/web/utils/test/fixtures/xml/k_020_fr-test.xml b/developer/src/common/web/utils/test/fixtures/xml/k_020_fr-test.xml
new file mode 100644
index 00000000000..22fce785389
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/k_020_fr-test.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/developer/src/common/web/utils/test/fixtures/xml/k_020_fr-test.xml.json b/developer/src/common/web/utils/test/fixtures/xml/k_020_fr-test.xml.json
new file mode 100644
index 00000000000..ab530dd0b4a
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/k_020_fr-test.xml.json
@@ -0,0 +1,538 @@
+{
+ "keyboardTest3": {
+ "$": {
+ "conformsTo": "techpreview"
+ },
+ "#name": "keyboardTest3",
+ "$$": [
+ {
+ "$": {
+ "keyboard": "k_020_fr.xml",
+ "author": "Team Keyboard",
+ "name": "fr-test-updated"
+ },
+ "#name": "info"
+ },
+ {
+ "$": {
+ "name": "simple-repertoire",
+ "chars": "[a b c d e \\u0022]",
+ "type": "simple"
+ },
+ "#name": "repertoire"
+ },
+ {
+ "$": {
+ "name": "chars-repertoire",
+ "chars": "[á é ó]",
+ "type": "gesture"
+ },
+ "#name": "repertoire"
+ },
+ {
+ "$": {
+ "name": "key-tests-updated"
+ },
+ "#name": "tests",
+ "$$": [
+ {
+ "$": {
+ "name": "key-test"
+ },
+ "#name": "test",
+ "$$": [
+ {
+ "$": {
+ "to": "abc\\u0022..."
+ },
+ "#name": "startContext"
+ },
+ {
+ "$": {
+ "key": "s"
+ },
+ "#name": "keystroke"
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...s"
+ },
+ "#name": "check"
+ },
+ {
+ "$": {
+ "key": "t"
+ },
+ "#name": "keystroke"
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...st"
+ },
+ "#name": "check"
+ },
+ {
+ "$": {
+ "key": "u"
+ },
+ "#name": "keystroke"
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...stu"
+ },
+ "#name": "check"
+ },
+ {
+ "$": {
+ "to": "v"
+ },
+ "#name": "emit"
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...stuv"
+ },
+ "#name": "check"
+ }
+ ],
+ "startContext": [
+ {
+ "$": {
+ "to": "abc\\u0022..."
+ }
+ }
+ ],
+ "keystroke": [
+ {
+ "$": {
+ "key": "s"
+ }
+ },
+ {
+ "$": {
+ "key": "t"
+ }
+ },
+ {
+ "$": {
+ "key": "u"
+ }
+ }
+ ],
+ "check": [
+ {
+ "$": {
+ "result": "abc\\u0022...s"
+ }
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...st"
+ }
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...stu"
+ }
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...stuv"
+ }
+ }
+ ],
+ "emit": [
+ {
+ "$": {
+ "to": "v"
+ }
+ }
+ ]
+ }
+ ],
+ "test": [
+ {
+ "$": {
+ "name": "key-test"
+ },
+ "$$": [
+ {
+ "$": {
+ "to": "abc\\u0022..."
+ },
+ "#name": "startContext"
+ },
+ {
+ "$": {
+ "key": "s"
+ },
+ "#name": "keystroke"
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...s"
+ },
+ "#name": "check"
+ },
+ {
+ "$": {
+ "key": "t"
+ },
+ "#name": "keystroke"
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...st"
+ },
+ "#name": "check"
+ },
+ {
+ "$": {
+ "key": "u"
+ },
+ "#name": "keystroke"
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...stu"
+ },
+ "#name": "check"
+ },
+ {
+ "$": {
+ "to": "v"
+ },
+ "#name": "emit"
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...stuv"
+ },
+ "#name": "check"
+ }
+ ],
+ "startContext": [
+ {
+ "$": {
+ "to": "abc\\u0022..."
+ }
+ }
+ ],
+ "keystroke": [
+ {
+ "$": {
+ "key": "s"
+ }
+ },
+ {
+ "$": {
+ "key": "t"
+ }
+ },
+ {
+ "$": {
+ "key": "u"
+ }
+ }
+ ],
+ "check": [
+ {
+ "$": {
+ "result": "abc\\u0022...s"
+ }
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...st"
+ }
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...stu"
+ }
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...stuv"
+ }
+ }
+ ],
+ "emit": [
+ {
+ "$": {
+ "to": "v"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "info": [
+ {
+ "$": {
+ "keyboard": "k_020_fr.xml",
+ "author": "Team Keyboard",
+ "name": "fr-test-updated"
+ }
+ }
+ ],
+ "repertoire": [
+ {
+ "$": {
+ "name": "simple-repertoire",
+ "chars": "[a b c d e \\u0022]",
+ "type": "simple"
+ }
+ },
+ {
+ "$": {
+ "name": "chars-repertoire",
+ "chars": "[á é ó]",
+ "type": "gesture"
+ }
+ }
+ ],
+ "tests": [
+ {
+ "$": {
+ "name": "key-tests-updated"
+ },
+ "$$": [
+ {
+ "$": {
+ "name": "key-test"
+ },
+ "#name": "test",
+ "$$": [
+ {
+ "$": {
+ "to": "abc\\u0022..."
+ },
+ "#name": "startContext"
+ },
+ {
+ "$": {
+ "key": "s"
+ },
+ "#name": "keystroke"
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...s"
+ },
+ "#name": "check"
+ },
+ {
+ "$": {
+ "key": "t"
+ },
+ "#name": "keystroke"
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...st"
+ },
+ "#name": "check"
+ },
+ {
+ "$": {
+ "key": "u"
+ },
+ "#name": "keystroke"
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...stu"
+ },
+ "#name": "check"
+ },
+ {
+ "$": {
+ "to": "v"
+ },
+ "#name": "emit"
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...stuv"
+ },
+ "#name": "check"
+ }
+ ],
+ "startContext": [
+ {
+ "$": {
+ "to": "abc\\u0022..."
+ }
+ }
+ ],
+ "keystroke": [
+ {
+ "$": {
+ "key": "s"
+ }
+ },
+ {
+ "$": {
+ "key": "t"
+ }
+ },
+ {
+ "$": {
+ "key": "u"
+ }
+ }
+ ],
+ "check": [
+ {
+ "$": {
+ "result": "abc\\u0022...s"
+ }
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...st"
+ }
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...stu"
+ }
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...stuv"
+ }
+ }
+ ],
+ "emit": [
+ {
+ "$": {
+ "to": "v"
+ }
+ }
+ ]
+ }
+ ],
+ "test": [
+ {
+ "$": {
+ "name": "key-test"
+ },
+ "$$": [
+ {
+ "$": {
+ "to": "abc\\u0022..."
+ },
+ "#name": "startContext"
+ },
+ {
+ "$": {
+ "key": "s"
+ },
+ "#name": "keystroke"
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...s"
+ },
+ "#name": "check"
+ },
+ {
+ "$": {
+ "key": "t"
+ },
+ "#name": "keystroke"
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...st"
+ },
+ "#name": "check"
+ },
+ {
+ "$": {
+ "key": "u"
+ },
+ "#name": "keystroke"
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...stu"
+ },
+ "#name": "check"
+ },
+ {
+ "$": {
+ "to": "v"
+ },
+ "#name": "emit"
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...stuv"
+ },
+ "#name": "check"
+ }
+ ],
+ "startContext": [
+ {
+ "$": {
+ "to": "abc\\u0022..."
+ }
+ }
+ ],
+ "keystroke": [
+ {
+ "$": {
+ "key": "s"
+ }
+ },
+ {
+ "$": {
+ "key": "t"
+ }
+ },
+ {
+ "$": {
+ "key": "u"
+ }
+ }
+ ],
+ "check": [
+ {
+ "$": {
+ "result": "abc\\u0022...s"
+ }
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...st"
+ }
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...stu"
+ }
+ },
+ {
+ "$": {
+ "result": "abc\\u0022...stuv"
+ }
+ }
+ ],
+ "emit": [
+ {
+ "$": {
+ "to": "v"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/developer/src/common/web/utils/test/fixtures/xml/k_020_fr.xml b/developer/src/common/web/utils/test/fixtures/xml/k_020_fr.xml
new file mode 100644
index 00000000000..671f1b8b9b6
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/k_020_fr.xml
@@ -0,0 +1,200 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/developer/src/common/web/utils/test/fixtures/xml/k_020_fr.xml.json b/developer/src/common/web/utils/test/fixtures/xml/k_020_fr.xml.json
new file mode 100644
index 00000000000..1e23ec4bdbd
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/k_020_fr.xml.json
@@ -0,0 +1,509 @@
+{
+ "keyboard3": {
+ "xmlns": "https://schemas.unicode.org/cldr/45/keyboard3",
+ "locale": "fr-t-k0-azerty",
+ "conformsTo": "45",
+ "locales": {
+ "locale": {
+ "id": "br"
+ }
+ },
+ "version": {
+ "number": "1.0.1"
+ },
+ "info": {
+ "author": "Team Keyboard",
+ "layout": "AZERTY",
+ "indicator": "FR",
+ "name": "French Test Updated"
+ },
+ "displays": {
+ "display": {
+ "output": "\\u{0300}",
+ "display": "\\u{02CB}"
+ },
+ "displayOptions": {
+ "baseCharacter": "x"
+ }
+ },
+ "keys": {
+ "import": [
+ {
+ "base": "cldr",
+ "path": "45/keys-Zyyy-punctuation.xml"
+ },
+ {
+ "base": "cldr",
+ "path": "45/keys-Zyyy-currency.xml"
+ }
+ ],
+ "key": [
+ {
+ "id": "shift",
+ "layerId": "shift"
+ },
+ {
+ "id": "numeric",
+ "layerId": "numeric"
+ },
+ {
+ "id": "symbol",
+ "layerId": "symbol"
+ },
+ {
+ "id": "base",
+ "layerId": "base"
+ },
+ {
+ "id": "bksp",
+ "gap": "true"
+ },
+ {
+ "id": "extra",
+ "gap": "true"
+ },
+ {
+ "id": "enter",
+ "output": "\\u{000A}"
+ },
+ {
+ "id": "u-grave",
+ "output": "ü"
+ },
+ {
+ "id": "e-grave",
+ "output": "é"
+ },
+ {
+ "id": "e-acute",
+ "output": "è"
+ },
+ {
+ "id": "c-cedilla",
+ "output": "ç"
+ },
+ {
+ "id": "a-grave",
+ "output": "à"
+ },
+ {
+ "id": "a-acute",
+ "output": "á"
+ },
+ {
+ "id": "a-caret",
+ "output": "â"
+ },
+ {
+ "id": "a-umlaut",
+ "output": "ä"
+ },
+ {
+ "id": "a-tilde",
+ "output": "ã"
+ },
+ {
+ "id": "a-ring",
+ "output": "å"
+ },
+ {
+ "id": "a-caron",
+ "output": "ā"
+ },
+ {
+ "id": "A-grave",
+ "output": "À"
+ },
+ {
+ "id": "A-acute",
+ "output": "Á"
+ },
+ {
+ "id": "A-caret",
+ "output": "Â"
+ },
+ {
+ "id": "A-umlaut",
+ "output": "Ä"
+ },
+ {
+ "id": "A-tilde",
+ "output": "Ã"
+ },
+ {
+ "id": "A-ring",
+ "output": "Å"
+ },
+ {
+ "id": "A-caron",
+ "output": "Ā"
+ },
+ {
+ "id": "bullet",
+ "output": "•"
+ },
+ {
+ "id": "umlaut",
+ "output": "¨"
+ },
+ {
+ "id": "sub-2",
+ "output": "₂"
+ },
+ {
+ "id": "super-2",
+ "output": "²",
+ "longPressKeyIds": "sub-2"
+ },
+ {
+ "id": "a",
+ "flickId": "a",
+ "output": "a",
+ "longPressKeyIds": "a-grave a-caret a-acute a-umlaut a-tilde a-ring a-caron",
+ "longPressDefaultKeyId": "a-caret"
+ },
+ {
+ "id": "A",
+ "flickId": "b",
+ "output": "A",
+ "longPressKeyIds": "A-grave A-caret A-acute A-umlaut a-tilde A-ring A-caron",
+ "longPressDefaultKeyId": "A-caret"
+ }
+ ]
+ },
+ "flicks": {
+ "flick": [
+ {
+ "id": "b",
+ "flickSegment": [
+ {
+ "directions": "nw",
+ "keyId": "A-grave"
+ },
+ {
+ "directions": "nw se",
+ "keyId": "A-acute"
+ },
+ {
+ "directions": "e",
+ "keyId": "A-caron"
+ },
+ {
+ "directions": "s",
+ "keyId": "numeric"
+ }
+ ]
+ },
+ {
+ "id": "a",
+ "flickSegment": [
+ {
+ "directions": "nw",
+ "keyId": "a-grave"
+ },
+ {
+ "directions": "nw se",
+ "keyId": "a-acute"
+ },
+ {
+ "directions": "e",
+ "keyId": "a-caron"
+ }
+ ]
+ }
+ ]
+ },
+ "layers": [
+ {
+ "formId": "iso",
+ "layer": [
+ {
+ "modifiers": "none",
+ "row": [
+ {
+ "keys": "super-2 amp e-grave double-quote apos open-paren hyphen e-acute underscore c-cedilla a-acute close-paren equal"
+ },
+ {
+ "keys": "a z e r t y u i o p caret dollar"
+ },
+ {
+ "keys": "q s d f g h j k l m u-grave asterisk"
+ },
+ {
+ "keys": "open-angle w x c v b n comma semi-colon colon bang"
+ },
+ {
+ "keys": "space"
+ }
+ ]
+ },
+ {
+ "modifiers": "shift",
+ "row": [
+ {
+ "keys": "1 2 3 4 5 6 7 8 9 0 degree plus"
+ },
+ {
+ "keys": "A Z E R T Y U I O P umlaut pound"
+ },
+ {
+ "keys": "Q S D F G H J K L M percent micro"
+ },
+ {
+ "keys": "close-angle W X C V B N question period slash section"
+ },
+ {
+ "keys": "space"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "formId": "touch",
+ "minDeviceWidth": "150",
+ "layer": [
+ {
+ "id": "base",
+ "row": [
+ {
+ "keys": "a z e r t y u i o p"
+ },
+ {
+ "keys": "q s d f g h j k l m"
+ },
+ {
+ "keys": "shift gap w x c v b n gap"
+ },
+ {
+ "keys": "numeric extra space enter"
+ }
+ ]
+ },
+ {
+ "id": "shift",
+ "row": [
+ {
+ "keys": "A Z E R T Y U I O P"
+ },
+ {
+ "keys": "Q S D F G H J K L M"
+ },
+ {
+ "keys": "base W X C V B N"
+ },
+ {
+ "keys": "numeric extra space enter"
+ }
+ ]
+ },
+ {
+ "id": "numeric",
+ "row": [
+ {
+ "keys": "1 2 3 4 5 6 7 8 9 0"
+ },
+ {
+ "keys": "hyphen slash colon semi-colon open-paren close-paren dollar amp at double-quote"
+ },
+ {
+ "keys": "symbol period comma question bang double-quote"
+ },
+ {
+ "keys": "base extra space enter"
+ }
+ ]
+ },
+ {
+ "id": "symbol",
+ "row": [
+ {
+ "keys": "open-square close-square open-curly close-curly hash percent caret asterisk plus equal"
+ },
+ {
+ "keys": "underscore backslash pipe tilde open-angle close-angle euro pound yen bullet"
+ },
+ {
+ "keys": "numeric period comma question bang double-quote"
+ },
+ {
+ "keys": "base extra space enter"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "transforms": {
+ "type": "simple",
+ "transformGroup": {
+ "transform": [
+ {
+ "from": "` ",
+ "to": "`"
+ },
+ {
+ "from": "`a",
+ "to": "à"
+ },
+ {
+ "from": "`A",
+ "to": "À"
+ },
+ {
+ "from": "`e",
+ "to": "è"
+ },
+ {
+ "from": "`E",
+ "to": "È"
+ },
+ {
+ "from": "`i",
+ "to": "ì"
+ },
+ {
+ "from": "`I",
+ "to": "Ì"
+ },
+ {
+ "from": "`o",
+ "to": "ò"
+ },
+ {
+ "from": "`O",
+ "to": "Ò"
+ },
+ {
+ "from": "`u",
+ "to": "ù"
+ },
+ {
+ "from": "`U",
+ "to": "Ù"
+ },
+ {
+ "from": "^ ",
+ "to": "^"
+ },
+ {
+ "from": "^a",
+ "to": "â"
+ },
+ {
+ "from": "^A",
+ "to": " "
+ },
+ {
+ "from": "^e",
+ "to": "ê"
+ },
+ {
+ "from": "^E",
+ "to": "Ê"
+ },
+ {
+ "from": "^i",
+ "to": "î"
+ },
+ {
+ "from": "^I",
+ "to": "Î"
+ },
+ {
+ "from": "^o",
+ "to": "ô"
+ },
+ {
+ "from": "^O",
+ "to": "Ô"
+ },
+ {
+ "from": "^u",
+ "to": "û"
+ },
+ {
+ "from": "^U",
+ "to": "Û"
+ },
+ {
+ "from": "¨ ",
+ "to": "¨"
+ },
+ {
+ "from": "¨a",
+ "to": "ä"
+ },
+ {
+ "from": "¨A",
+ "to": "Ä"
+ },
+ {
+ "from": "¨e",
+ "to": "ë"
+ },
+ {
+ "from": "¨E",
+ "to": "Ë"
+ },
+ {
+ "from": "¨i",
+ "to": "ï"
+ },
+ {
+ "from": "¨I",
+ "to": "Ï"
+ },
+ {
+ "from": "¨o",
+ "to": "ö"
+ },
+ {
+ "from": "¨O",
+ "to": "Ö"
+ },
+ {
+ "from": "¨u",
+ "to": "ü"
+ },
+ {
+ "from": "¨U",
+ "to": "Ü"
+ },
+ {
+ "from": "¨y",
+ "to": "ÿ"
+ },
+ {
+ "from": "~ ",
+ "to": "~"
+ },
+ {
+ "from": "~a",
+ "to": "ã"
+ },
+ {
+ "from": "~A",
+ "to": "Ã"
+ },
+ {
+ "from": "~n",
+ "to": "ñ"
+ },
+ {
+ "from": "~N",
+ "to": "Ñ"
+ },
+ {
+ "from": "~o",
+ "to": "õ"
+ },
+ {
+ "from": "~O",
+ "to": "Õ"
+ }
+ ]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kpj b/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kpj
new file mode 100644
index 00000000000..4b239df20ce
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kpj
@@ -0,0 +1,187 @@
+
+
+
+ $PROJECTPATH\build
+ True
+ True
+ False
+ keyboard
+
+
+
+ id_f347675c33d2e6b1c705c787fad4941a
+ khmer_angkor.kmn
+ source\khmer_angkor.kmn
+ 1.3
+ .kmn
+
+ Khmer Angkor
+ © 2015-2022 SIL International
+ More than just a Khmer Unicode keyboard.
+
+
+
+ id_8d4eb765f80c9f2b0f769cf4e4aaa456
+ khmer_angkor.kps
+ source\khmer_angkor.kps
+
+ .kps
+
+ Khmer Angkor
+ © 2015-2022 SIL International
+
+
+
+ id_8a1efc7c4ab7cfece8aedd847679ca27
+ khmer_angkor.ico
+ source\khmer_angkor.ico
+
+ .ico
+ id_f347675c33d2e6b1c705c787fad4941a
+
+
+ id_8dc195db32d1fd0514de0ad51fff5df0
+ khmer_angkor.js
+ source\..\build\khmer_angkor.js
+
+ .js
+ id_8d4eb765f80c9f2b0f769cf4e4aaa456
+
+
+ id_10596632fcbf4138d24bcccf53e6ae01
+ khmer_angkor.kvk
+ source\..\build\khmer_angkor.kvk
+
+ .kvk
+ id_8d4eb765f80c9f2b0f769cf4e4aaa456
+
+
+ id_0a851f95ce553ecd62cbee6c32ced68f
+ khmer_angkor.kmx
+ source\..\build\khmer_angkor.kmx
+
+ .kmx
+ id_8d4eb765f80c9f2b0f769cf4e4aaa456
+
+
+ id_d8b6eb05f4b7e2945c10e04c1f49e4c8
+ keyboard_layout.png
+ source\welcome\keyboard_layout.png
+
+ .png
+ id_8d4eb765f80c9f2b0f769cf4e4aaa456
+
+
+ id_724e5b4c63f10bc0abf7077f7c3172fc
+ welcome.htm
+ source\welcome\welcome.htm
+
+ .htm
+ id_8d4eb765f80c9f2b0f769cf4e4aaa456
+
+
+ id_35857cb2b54f123612735ec948400082
+ FONTLOG.txt
+ source\..\..\..\shared\fonts\khmer\mondulkiri\FONTLOG.txt
+
+ .txt
+ id_8d4eb765f80c9f2b0f769cf4e4aaa456
+
+
+ id_7e3afe5bb59b888b08b48cd5817d8de4
+ Mondulkiri-B.ttf
+ source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-B.ttf
+
+ .ttf
+ id_8d4eb765f80c9f2b0f769cf4e4aaa456
+
+
+ id_b9734e80f86c69ea5ae4dfa9f0083d09
+ Mondulkiri-BI.ttf
+ source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-BI.ttf
+
+ .ttf
+ id_8d4eb765f80c9f2b0f769cf4e4aaa456
+
+
+ id_25abe4d2b0abc03a5be5b666a8de776e
+ Mondulkiri-I.ttf
+ source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-I.ttf
+
+ .ttf
+ id_8d4eb765f80c9f2b0f769cf4e4aaa456
+
+
+ id_b766568498108eee46ed1601ff69c47d
+ Mondulkiri-R.ttf
+ source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-R.ttf
+
+ .ttf
+ id_8d4eb765f80c9f2b0f769cf4e4aaa456
+
+
+ id_84544d04133cab3dbfc86b91ad1a4e17
+ OFL.txt
+ source\..\..\..\shared\fonts\khmer\mondulkiri\OFL.txt
+
+ .txt
+ id_8d4eb765f80c9f2b0f769cf4e4aaa456
+
+
+ id_0c33fbefd1c20f487b1bea2343b3bb2c
+ OFL-FAQ.txt
+ source\..\..\..\shared\fonts\khmer\mondulkiri\OFL-FAQ.txt
+
+ .txt
+ id_8d4eb765f80c9f2b0f769cf4e4aaa456
+
+
+ id_a59d89fca36a310147645fa2604e521b
+ KAK_Documentation_EN.pdf
+ source\welcome\KAK_Documentation_EN.pdf
+
+ .pdf
+ id_8d4eb765f80c9f2b0f769cf4e4aaa456
+
+
+ id_5643c4cd3933b3ada0b4af6579305ec4
+ KAK_Documentation_KH.pdf
+ source\welcome\KAK_Documentation_KH.pdf
+
+ .pdf
+ id_8d4eb765f80c9f2b0f769cf4e4aaa456
+
+
+ id_8da344c4cea6f467013357fe099006f5
+ readme.htm
+ source\readme.htm
+
+ .htm
+ id_8d4eb765f80c9f2b0f769cf4e4aaa456
+
+
+ id_acb0dd94c60e345d999670e999cbd159
+ image002.png
+ source\welcome\image002.png
+
+ .png
+ id_8d4eb765f80c9f2b0f769cf4e4aaa456
+
+
+ id_4edf70bc019f05b5ad39a2ea727ad547
+ khmer_busra_kbd.ttf
+ source\..\..\..\shared\fonts\khmer\busrakbd\khmer_busra_kbd.ttf
+
+ .ttf
+ id_8d4eb765f80c9f2b0f769cf4e4aaa456
+
+
+ id_bc823844e4399751e1867016801f7327
+ splash.gif
+ source\splash.gif
+
+ .gif
+ id_8d4eb765f80c9f2b0f769cf4e4aaa456
+
+
+
diff --git a/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kpj.json b/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kpj.json
new file mode 100644
index 00000000000..8f2310f821c
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kpj.json
@@ -0,0 +1,190 @@
+{
+ "KeymanDeveloperProject": {
+ "Options": {
+ "BuildPath": "$PROJECTPATH\\build",
+ "CompilerWarningsAsErrors": "True",
+ "WarnDeprecatedCode": "True",
+ "CheckFilenameConventions": "False",
+ "ProjectType": "keyboard"
+ },
+ "Files": {
+ "File": [
+ {
+ "ID": "id_f347675c33d2e6b1c705c787fad4941a",
+ "Filename": "khmer_angkor.kmn",
+ "Filepath": "source\\khmer_angkor.kmn",
+ "FileVersion": "1.3",
+ "FileType": ".kmn",
+ "Details": {
+ "Name": "Khmer Angkor",
+ "Copyright": "© 2015-2022 SIL International",
+ "Message": "More than just a Khmer Unicode keyboard."
+ }
+ },
+ {
+ "ID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456",
+ "Filename": "khmer_angkor.kps",
+ "Filepath": "source\\khmer_angkor.kps",
+ "FileVersion": "",
+ "FileType": ".kps",
+ "Details": {
+ "Name": "Khmer Angkor",
+ "Copyright": "© 2015-2022 SIL International"
+ }
+ },
+ {
+ "ID": "id_8a1efc7c4ab7cfece8aedd847679ca27",
+ "Filename": "khmer_angkor.ico",
+ "Filepath": "source\\khmer_angkor.ico",
+ "FileVersion": "",
+ "FileType": ".ico",
+ "ParentFileID": "id_f347675c33d2e6b1c705c787fad4941a"
+ },
+ {
+ "ID": "id_8dc195db32d1fd0514de0ad51fff5df0",
+ "Filename": "khmer_angkor.js",
+ "Filepath": "source\\..\\build\\khmer_angkor.js",
+ "FileVersion": "",
+ "FileType": ".js",
+ "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456"
+ },
+ {
+ "ID": "id_10596632fcbf4138d24bcccf53e6ae01",
+ "Filename": "khmer_angkor.kvk",
+ "Filepath": "source\\..\\build\\khmer_angkor.kvk",
+ "FileVersion": "",
+ "FileType": ".kvk",
+ "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456"
+ },
+ {
+ "ID": "id_0a851f95ce553ecd62cbee6c32ced68f",
+ "Filename": "khmer_angkor.kmx",
+ "Filepath": "source\\..\\build\\khmer_angkor.kmx",
+ "FileVersion": "",
+ "FileType": ".kmx",
+ "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456"
+ },
+ {
+ "ID": "id_d8b6eb05f4b7e2945c10e04c1f49e4c8",
+ "Filename": "keyboard_layout.png",
+ "Filepath": "source\\welcome\\keyboard_layout.png",
+ "FileVersion": "",
+ "FileType": ".png",
+ "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456"
+ },
+ {
+ "ID": "id_724e5b4c63f10bc0abf7077f7c3172fc",
+ "Filename": "welcome.htm",
+ "Filepath": "source\\welcome\\welcome.htm",
+ "FileVersion": "",
+ "FileType": ".htm",
+ "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456"
+ },
+ {
+ "ID": "id_35857cb2b54f123612735ec948400082",
+ "Filename": "FONTLOG.txt",
+ "Filepath": "source\\..\\..\\..\\shared\\fonts\\khmer\\mondulkiri\\FONTLOG.txt",
+ "FileVersion": "",
+ "FileType": ".txt",
+ "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456"
+ },
+ {
+ "ID": "id_7e3afe5bb59b888b08b48cd5817d8de4",
+ "Filename": "Mondulkiri-B.ttf",
+ "Filepath": "source\\..\\..\\..\\shared\\fonts\\khmer\\mondulkiri\\Mondulkiri-B.ttf",
+ "FileVersion": "",
+ "FileType": ".ttf",
+ "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456"
+ },
+ {
+ "ID": "id_b9734e80f86c69ea5ae4dfa9f0083d09",
+ "Filename": "Mondulkiri-BI.ttf",
+ "Filepath": "source\\..\\..\\..\\shared\\fonts\\khmer\\mondulkiri\\Mondulkiri-BI.ttf",
+ "FileVersion": "",
+ "FileType": ".ttf",
+ "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456"
+ },
+ {
+ "ID": "id_25abe4d2b0abc03a5be5b666a8de776e",
+ "Filename": "Mondulkiri-I.ttf",
+ "Filepath": "source\\..\\..\\..\\shared\\fonts\\khmer\\mondulkiri\\Mondulkiri-I.ttf",
+ "FileVersion": "",
+ "FileType": ".ttf",
+ "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456"
+ },
+ {
+ "ID": "id_b766568498108eee46ed1601ff69c47d",
+ "Filename": "Mondulkiri-R.ttf",
+ "Filepath": "source\\..\\..\\..\\shared\\fonts\\khmer\\mondulkiri\\Mondulkiri-R.ttf",
+ "FileVersion": "",
+ "FileType": ".ttf",
+ "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456"
+ },
+ {
+ "ID": "id_84544d04133cab3dbfc86b91ad1a4e17",
+ "Filename": "OFL.txt",
+ "Filepath": "source\\..\\..\\..\\shared\\fonts\\khmer\\mondulkiri\\OFL.txt",
+ "FileVersion": "",
+ "FileType": ".txt",
+ "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456"
+ },
+ {
+ "ID": "id_0c33fbefd1c20f487b1bea2343b3bb2c",
+ "Filename": "OFL-FAQ.txt",
+ "Filepath": "source\\..\\..\\..\\shared\\fonts\\khmer\\mondulkiri\\OFL-FAQ.txt",
+ "FileVersion": "",
+ "FileType": ".txt",
+ "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456"
+ },
+ {
+ "ID": "id_a59d89fca36a310147645fa2604e521b",
+ "Filename": "KAK_Documentation_EN.pdf",
+ "Filepath": "source\\welcome\\KAK_Documentation_EN.pdf",
+ "FileVersion": "",
+ "FileType": ".pdf",
+ "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456"
+ },
+ {
+ "ID": "id_5643c4cd3933b3ada0b4af6579305ec4",
+ "Filename": "KAK_Documentation_KH.pdf",
+ "Filepath": "source\\welcome\\KAK_Documentation_KH.pdf",
+ "FileVersion": "",
+ "FileType": ".pdf",
+ "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456"
+ },
+ {
+ "ID": "id_8da344c4cea6f467013357fe099006f5",
+ "Filename": "readme.htm",
+ "Filepath": "source\\readme.htm",
+ "FileVersion": "",
+ "FileType": ".htm",
+ "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456"
+ },
+ {
+ "ID": "id_acb0dd94c60e345d999670e999cbd159",
+ "Filename": "image002.png",
+ "Filepath": "source\\welcome\\image002.png",
+ "FileVersion": "",
+ "FileType": ".png",
+ "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456"
+ },
+ {
+ "ID": "id_4edf70bc019f05b5ad39a2ea727ad547",
+ "Filename": "khmer_busra_kbd.ttf",
+ "Filepath": "source\\..\\..\\..\\shared\\fonts\\khmer\\busrakbd\\khmer_busra_kbd.ttf",
+ "FileVersion": "",
+ "FileType": ".ttf",
+ "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456"
+ },
+ {
+ "ID": "id_bc823844e4399751e1867016801f7327",
+ "Filename": "splash.gif",
+ "Filepath": "source\\splash.gif",
+ "FileVersion": "",
+ "FileType": ".gif",
+ "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456"
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kvks b/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kvks
new file mode 100644
index 00000000000..e9c38a464dd
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kvks
@@ -0,0 +1,206 @@
+
+
+
+ 10.0
+ khmer_angkor
+
+
+
+
+
+
+ ឞ
+ ឝ
+ ៈ
+ ឳ
+ ឨ
+ ឩ
+ ឰ
+ ឫ
+ ឦ
+ ឱ
+ ឯ
+
+
+ ៜ
+
+ ៖
+ ៙
+ ៚
+
+ ៘
+
+
+ ]
+ [
+ /
+ .
+ ‘
+ +
+ &
+ ’
+ *
+ @
+ \
+ }
+ {
+ -
+ ÷
+ :
+ ,
+ ≈
+ ;
+ <
+ #
+ >
+ ×
+ $
+ €
+
+
+
+ ៸
+ ៰
+ ៱
+ ៲
+ ៳
+ ៴
+ ៵
+ ៶
+ ៷
+ ៹
+ ᧿
+ ᧾
+ ᧪
+ ᧫
+ ᧶
+ ᧵
+ ᧬
+ ᧷
+ ᧥
+ ᧸
+ ᧡
+ ᧻
+ ᧹
+ ᧮
+ ᧢
+ ᧯
+ ᧰
+ ᧦
+ ᧱
+ ᧤
+ ᧭
+ ᧣
+ ᧧
+ ᧠
+ ᧺
+ ᧲
+ ᧳
+ ᧴
+ ᧩
+ ᧨
+ ᧼
+ ᧽
+
+
+
+
+
+ ឥ
+ ។
+
+ ០
+ ១
+ ២
+ ៣
+ ៤
+ ៥
+ ៦
+ ៧
+ ៨
+ ៩
+
+ ឲ
+
+ ឮ
+ ឪ
+ «
+
+ ប
+ ច
+ ដ
+
+ ថ
+ ង
+ ហ
+
+
+ ក
+ ល
+ ម
+ ន
+
+ ផ
+ ឆ
+ រ
+ ស
+ ត
+
+ វ
+
+ ខ
+ យ
+ ឋ
+
+
+
+ !
+
+ "
+ ៛
+ %
+
+ (
+ )
+
+ =
+
+ ៕
+ ?
+ ៗ
+
+ ព
+ ជ
+ ឌ
+
+ ធ
+ អ
+ ះ
+
+ ញ
+ គ
+ ឡ
+
+ ណ
+
+ ភ
+ ឈ
+ ឬ
+
+ ទ
+
+
+
+ ឃ
+
+ ឍ
+
+
+
+ ឭ
+ ឧ
+ »
+
+
+
+
diff --git a/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kvks.json b/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kvks.json
new file mode 100644
index 00000000000..a20c8b67518
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kvks.json
@@ -0,0 +1,1171 @@
+{
+ "visualkeyboard": {
+
+ "header": {
+
+ "version": "10.0",
+ "kbdname": "khmer_angkor",
+ "flags": {
+
+ "usealtgr": ""
+ }
+ },
+ "encoding": {
+
+ "$": {
+ "name": "unicode",
+ "fontname": "Khmer Busra Kbd",
+ "fontsize": "16"
+ },
+ "layer": [
+ {
+
+ "$": {
+ "shift": "RA"
+ },
+ "key": [
+ {
+ "_": "ឞ",
+ "$": {
+ "vkey": "K_B"
+ }
+ },
+ {
+ "_": "ឝ",
+ "$": {
+ "vkey": "K_K"
+ }
+ },
+ {
+ "_": "ៈ",
+ "$": {
+ "vkey": "K_QUOTE"
+ }
+ },
+ {
+ "_": "ឳ",
+ "$": {
+ "vkey": "K_RBRKT"
+ }
+ },
+ {
+ "_": "ឨ",
+ "$": {
+ "vkey": "K_T"
+ }
+ },
+ {
+ "_": "ឩ",
+ "$": {
+ "vkey": "K_LBRKT"
+ }
+ },
+ {
+ "_": "ឰ",
+ "$": {
+ "vkey": "K_P"
+ }
+ },
+ {
+ "_": "ឫ",
+ "$": {
+ "vkey": "K_R"
+ }
+ },
+ {
+ "_": "ឦ",
+ "$": {
+ "vkey": "K_I"
+ }
+ },
+ {
+ "_": "ឱ",
+ "$": {
+ "vkey": "K_O"
+ }
+ },
+ {
+ "_": "ឯ",
+ "$": {
+ "vkey": "K_E"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_3"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_W"
+ }
+ },
+ {
+ "_": "ៜ",
+ "$": {
+ "vkey": "K_Q"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_EQUAL"
+ }
+ },
+ {
+ "_": "៖",
+ "$": {
+ "vkey": "K_COLON"
+ }
+ },
+ {
+ "_": "៙",
+ "$": {
+ "vkey": "K_6"
+ }
+ },
+ {
+ "_": "៚",
+ "$": {
+ "vkey": "K_7"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_M"
+ }
+ },
+ {
+ "_": "៘",
+ "$": {
+ "vkey": "K_L"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_1"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_BKQUOTE"
+ }
+ },
+ {
+ "_": "]",
+ "$": {
+ "vkey": "K_U"
+ }
+ },
+ {
+ "_": "[",
+ "$": {
+ "vkey": "K_Y"
+ }
+ },
+ {
+ "_": "/",
+ "$": {
+ "vkey": "K_SLASH"
+ }
+ },
+ {
+ "_": ".",
+ "$": {
+ "vkey": "K_PERIOD"
+ }
+ },
+ {
+ "_": "‘",
+ "$": {
+ "vkey": "K_H"
+ }
+ },
+ {
+ "_": "+",
+ "$": {
+ "vkey": "K_A"
+ }
+ },
+ {
+ "_": "&",
+ "$": {
+ "vkey": "K_V"
+ }
+ },
+ {
+ "_": "’",
+ "$": {
+ "vkey": "K_J"
+ }
+ },
+ {
+ "_": "*",
+ "$": {
+ "vkey": "K_8"
+ }
+ },
+ {
+ "_": "@",
+ "$": {
+ "vkey": "K_2"
+ }
+ },
+ {
+ "_": "\\",
+ "$": {
+ "vkey": "K_BKSLASH"
+ }
+ },
+ {
+ "_": "}",
+ "$": {
+ "vkey": "K_0"
+ }
+ },
+ {
+ "_": "{",
+ "$": {
+ "vkey": "K_9"
+ }
+ },
+ {
+ "_": "-",
+ "$": {
+ "vkey": "K_S"
+ }
+ },
+ {
+ "_": "÷",
+ "$": {
+ "vkey": "K_F"
+ }
+ },
+ {
+ "_": ":",
+ "$": {
+ "vkey": "K_G"
+ }
+ },
+ {
+ "_": ",",
+ "$": {
+ "vkey": "K_COMMA"
+ }
+ },
+ {
+ "_": "≈",
+ "$": {
+ "vkey": "K_HYPHEN"
+ }
+ },
+ {
+ "_": ";",
+ "$": {
+ "vkey": "K_N"
+ }
+ },
+ {
+ "_": "<",
+ "$": {
+ "vkey": "K_Z"
+ }
+ },
+ {
+ "_": "#",
+ "$": {
+ "vkey": "K_C"
+ }
+ },
+ {
+ "_": ">",
+ "$": {
+ "vkey": "K_X"
+ }
+ },
+ {
+ "_": "×",
+ "$": {
+ "vkey": "K_D"
+ }
+ },
+ {
+ "_": "$",
+ "$": {
+ "vkey": "K_4"
+ }
+ },
+ {
+ "_": "€",
+ "$": {
+ "vkey": "K_5"
+ }
+ },
+ {
+ "_": " ",
+ "$": {
+ "vkey": "K_SPACE"
+ }
+ }
+ ]
+ },
+ {
+
+ "$": {
+ "shift": "SRA"
+ },
+ "key": [
+ {
+ "_": "៸",
+ "$": {
+ "vkey": "K_8"
+ }
+ },
+ {
+ "_": "៰",
+ "$": {
+ "vkey": "K_0"
+ }
+ },
+ {
+ "_": "៱",
+ "$": {
+ "vkey": "K_1"
+ }
+ },
+ {
+ "_": "៲",
+ "$": {
+ "vkey": "K_2"
+ }
+ },
+ {
+ "_": "៳",
+ "$": {
+ "vkey": "K_3"
+ }
+ },
+ {
+ "_": "៴",
+ "$": {
+ "vkey": "K_4"
+ }
+ },
+ {
+ "_": "៵",
+ "$": {
+ "vkey": "K_5"
+ }
+ },
+ {
+ "_": "៶",
+ "$": {
+ "vkey": "K_6"
+ }
+ },
+ {
+ "_": "៷",
+ "$": {
+ "vkey": "K_7"
+ }
+ },
+ {
+ "_": "៹",
+ "$": {
+ "vkey": "K_9"
+ }
+ },
+ {
+ "_": "᧿",
+ "$": {
+ "vkey": "K_PERIOD"
+ }
+ },
+ {
+ "_": "᧾",
+ "$": {
+ "vkey": "K_COMMA"
+ }
+ },
+ {
+ "_": "᧪",
+ "$": {
+ "vkey": "K_LBRKT"
+ }
+ },
+ {
+ "_": "᧫",
+ "$": {
+ "vkey": "K_RBRKT"
+ }
+ },
+ {
+ "_": "᧶",
+ "$": {
+ "vkey": "K_QUOTE"
+ }
+ },
+ {
+ "_": "᧵",
+ "$": {
+ "vkey": "K_COLON"
+ }
+ },
+ {
+ "_": "᧬",
+ "$": {
+ "vkey": "K_A"
+ }
+ },
+ {
+ "_": "᧷",
+ "$": {
+ "vkey": "K_Z"
+ }
+ },
+ {
+ "_": "᧥",
+ "$": {
+ "vkey": "K_Y"
+ }
+ },
+ {
+ "_": "᧸",
+ "$": {
+ "vkey": "K_X"
+ }
+ },
+ {
+ "_": "᧡",
+ "$": {
+ "vkey": "K_W"
+ }
+ },
+ {
+ "_": "᧻",
+ "$": {
+ "vkey": "K_B"
+ }
+ },
+ {
+ "_": "᧹",
+ "$": {
+ "vkey": "K_C"
+ }
+ },
+ {
+ "_": "᧮",
+ "$": {
+ "vkey": "K_D"
+ }
+ },
+ {
+ "_": "᧢",
+ "$": {
+ "vkey": "K_E"
+ }
+ },
+ {
+ "_": "᧯",
+ "$": {
+ "vkey": "K_F"
+ }
+ },
+ {
+ "_": "᧰",
+ "$": {
+ "vkey": "K_G"
+ }
+ },
+ {
+ "_": "᧦",
+ "$": {
+ "vkey": "K_U"
+ }
+ },
+ {
+ "_": "᧱",
+ "$": {
+ "vkey": "K_H"
+ }
+ },
+ {
+ "_": "᧤",
+ "$": {
+ "vkey": "K_T"
+ }
+ },
+ {
+ "_": "᧭",
+ "$": {
+ "vkey": "K_S"
+ }
+ },
+ {
+ "_": "᧣",
+ "$": {
+ "vkey": "K_R"
+ }
+ },
+ {
+ "_": "᧧",
+ "$": {
+ "vkey": "K_I"
+ }
+ },
+ {
+ "_": "᧠",
+ "$": {
+ "vkey": "K_Q"
+ }
+ },
+ {
+ "_": "᧺",
+ "$": {
+ "vkey": "K_V"
+ }
+ },
+ {
+ "_": "᧲",
+ "$": {
+ "vkey": "K_J"
+ }
+ },
+ {
+ "_": "᧳",
+ "$": {
+ "vkey": "K_K"
+ }
+ },
+ {
+ "_": "᧴",
+ "$": {
+ "vkey": "K_L"
+ }
+ },
+ {
+ "_": "᧩",
+ "$": {
+ "vkey": "K_P"
+ }
+ },
+ {
+ "_": "᧨",
+ "$": {
+ "vkey": "K_O"
+ }
+ },
+ {
+ "_": "᧼",
+ "$": {
+ "vkey": "K_N"
+ }
+ },
+ {
+ "_": "᧽",
+ "$": {
+ "vkey": "K_M"
+ }
+ }
+ ]
+ },
+ {
+
+ "$": {
+ "shift": ""
+ },
+ "key": [
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_SPACE"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_QUOTE"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_COMMA"
+ }
+ },
+ {
+ "_": "ឥ",
+ "$": {
+ "vkey": "K_HYPHEN"
+ }
+ },
+ {
+ "_": "។",
+ "$": {
+ "vkey": "K_PERIOD"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_SLASH"
+ }
+ },
+ {
+ "_": "០",
+ "$": {
+ "vkey": "K_0"
+ }
+ },
+ {
+ "_": "១",
+ "$": {
+ "vkey": "K_1"
+ }
+ },
+ {
+ "_": "២",
+ "$": {
+ "vkey": "K_2"
+ }
+ },
+ {
+ "_": "៣",
+ "$": {
+ "vkey": "K_3"
+ }
+ },
+ {
+ "_": "៤",
+ "$": {
+ "vkey": "K_4"
+ }
+ },
+ {
+ "_": "៥",
+ "$": {
+ "vkey": "K_5"
+ }
+ },
+ {
+ "_": "៦",
+ "$": {
+ "vkey": "K_6"
+ }
+ },
+ {
+ "_": "៧",
+ "$": {
+ "vkey": "K_7"
+ }
+ },
+ {
+ "_": "៨",
+ "$": {
+ "vkey": "K_8"
+ }
+ },
+ {
+ "_": "៩",
+ "$": {
+ "vkey": "K_9"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_COLON"
+ }
+ },
+ {
+ "_": "ឲ",
+ "$": {
+ "vkey": "K_EQUAL"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_LBRKT"
+ }
+ },
+ {
+ "_": "ឮ",
+ "$": {
+ "vkey": "K_BKSLASH"
+ }
+ },
+ {
+ "_": "ឪ",
+ "$": {
+ "vkey": "K_RBRKT"
+ }
+ },
+ {
+ "_": "«",
+ "$": {
+ "vkey": "K_BKQUOTE"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_A"
+ }
+ },
+ {
+ "_": "ប",
+ "$": {
+ "vkey": "K_B"
+ }
+ },
+ {
+ "_": "ច",
+ "$": {
+ "vkey": "K_C"
+ }
+ },
+ {
+ "_": "ដ",
+ "$": {
+ "vkey": "K_D"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_E"
+ }
+ },
+ {
+ "_": "ថ",
+ "$": {
+ "vkey": "K_F"
+ }
+ },
+ {
+ "_": "ង",
+ "$": {
+ "vkey": "K_G"
+ }
+ },
+ {
+ "_": "ហ",
+ "$": {
+ "vkey": "K_H"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_I"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_J"
+ }
+ },
+ {
+ "_": "ក",
+ "$": {
+ "vkey": "K_K"
+ }
+ },
+ {
+ "_": "ល",
+ "$": {
+ "vkey": "K_L"
+ }
+ },
+ {
+ "_": "ម",
+ "$": {
+ "vkey": "K_M"
+ }
+ },
+ {
+ "_": "ន",
+ "$": {
+ "vkey": "K_N"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_O"
+ }
+ },
+ {
+ "_": "ផ",
+ "$": {
+ "vkey": "K_P"
+ }
+ },
+ {
+ "_": "ឆ",
+ "$": {
+ "vkey": "K_Q"
+ }
+ },
+ {
+ "_": "រ",
+ "$": {
+ "vkey": "K_R"
+ }
+ },
+ {
+ "_": "ស",
+ "$": {
+ "vkey": "K_S"
+ }
+ },
+ {
+ "_": "ត",
+ "$": {
+ "vkey": "K_T"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_U"
+ }
+ },
+ {
+ "_": "វ",
+ "$": {
+ "vkey": "K_V"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_W"
+ }
+ },
+ {
+ "_": "ខ",
+ "$": {
+ "vkey": "K_X"
+ }
+ },
+ {
+ "_": "យ",
+ "$": {
+ "vkey": "K_Y"
+ }
+ },
+ {
+ "_": "ឋ",
+ "$": {
+ "vkey": "K_Z"
+ }
+ }
+ ]
+ },
+ {
+
+ "$": {
+ "shift": "S"
+ },
+ "key": [
+ {
+ "$": {
+ "vkey": "K_SPACE"
+ }
+ },
+ {
+ "_": "!",
+ "$": {
+ "vkey": "K_1"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_QUOTE"
+ }
+ },
+ {
+ "_": "\"",
+ "$": {
+ "vkey": "K_3"
+ }
+ },
+ {
+ "_": "៛",
+ "$": {
+ "vkey": "K_4"
+ }
+ },
+ {
+ "_": "%",
+ "$": {
+ "vkey": "K_5"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_7"
+ }
+ },
+ {
+ "_": "(",
+ "$": {
+ "vkey": "K_9"
+ }
+ },
+ {
+ "_": ")",
+ "$": {
+ "vkey": "K_0"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_8"
+ }
+ },
+ {
+ "_": "=",
+ "$": {
+ "vkey": "K_EQUAL"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_COLON"
+ }
+ },
+ {
+ "_": "៕",
+ "$": {
+ "vkey": "K_PERIOD"
+ }
+ },
+ {
+ "_": "?",
+ "$": {
+ "vkey": "K_SLASH"
+ }
+ },
+ {
+ "_": "ៗ",
+ "$": {
+ "vkey": "K_2"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_A"
+ }
+ },
+ {
+ "_": "ព",
+ "$": {
+ "vkey": "K_B"
+ }
+ },
+ {
+ "_": "ជ",
+ "$": {
+ "vkey": "K_C"
+ }
+ },
+ {
+ "_": "ឌ",
+ "$": {
+ "vkey": "K_D"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_E"
+ }
+ },
+ {
+ "_": "ធ",
+ "$": {
+ "vkey": "K_F"
+ }
+ },
+ {
+ "_": "អ",
+ "$": {
+ "vkey": "K_G"
+ }
+ },
+ {
+ "_": "ះ",
+ "$": {
+ "vkey": "K_H"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_I"
+ }
+ },
+ {
+ "_": "ញ",
+ "$": {
+ "vkey": "K_J"
+ }
+ },
+ {
+ "_": "គ",
+ "$": {
+ "vkey": "K_K"
+ }
+ },
+ {
+ "_": "ឡ",
+ "$": {
+ "vkey": "K_L"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_M"
+ }
+ },
+ {
+ "_": "ណ",
+ "$": {
+ "vkey": "K_N"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_O"
+ }
+ },
+ {
+ "_": "ភ",
+ "$": {
+ "vkey": "K_P"
+ }
+ },
+ {
+ "_": "ឈ",
+ "$": {
+ "vkey": "K_Q"
+ }
+ },
+ {
+ "_": "ឬ",
+ "$": {
+ "vkey": "K_R"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_S"
+ }
+ },
+ {
+ "_": "ទ",
+ "$": {
+ "vkey": "K_T"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_U"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_V"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_W"
+ }
+ },
+ {
+ "_": "ឃ",
+ "$": {
+ "vkey": "K_X"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_Y"
+ }
+ },
+ {
+ "_": "ឍ",
+ "$": {
+ "vkey": "K_Z"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_6"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_HYPHEN"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_LBRKT"
+ }
+ },
+ {
+ "_": "ឭ",
+ "$": {
+ "vkey": "K_BKSLASH"
+ }
+ },
+ {
+ "_": "ឧ",
+ "$": {
+ "vkey": "K_RBRKT"
+ }
+ },
+ {
+ "_": "»",
+ "$": {
+ "vkey": "K_BKQUOTE"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_COMMA"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
diff --git a/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor2.kvks b/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor2.kvks
new file mode 100644
index 00000000000..945477d3fee
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor2.kvks
@@ -0,0 +1,206 @@
+
+
+
+ 10.0
+ khmer_angkor
+
+
+
+
+
+
+ ឞ
+ ឝ
+ ៈ
+ ឳ
+ ឨ
+ ឩ
+ ឰ
+ ឫ
+ ឦ
+ ឱ
+ ឯ
+
+
+ ៜ
+
+ ៖
+ ៙
+ ៚
+
+ ៘
+
+
+ ]
+ [
+ /
+ .
+ ‘
+ +
+ &
+ ’
+ *
+ @
+ \
+ }
+ {
+ -
+ ÷
+ :
+ ,
+ ≈
+ ;
+ <
+ #
+ >
+ ×
+ $
+ €
+
+
+
+ ៸
+ ៰
+ ៱
+ ៲
+ ៳
+ ៴
+ ៵
+ ៶
+ ៷
+ ៹
+ ᧿
+ ᧾
+ ᧪
+ ᧫
+ ᧶
+ ᧵
+ ᧬
+ ᧷
+ ᧥
+ ᧸
+ ᧡
+ ᧻
+ ᧹
+ ᧮
+ ᧢
+ ᧯
+ ᧰
+ ᧦
+ ᧱
+ ᧤
+ ᧭
+ ᧣
+ ᧧
+ ᧠
+ ᧺
+ ᧲
+ ᧳
+ ᧴
+ ᧩
+ ᧨
+ ᧼
+ ᧽
+
+
+
+
+
+ ឥ
+ ។
+
+ ០
+ ១
+ ២
+ ៣
+ ៤
+ ៥
+ ៦
+ ៧
+ ៨
+ ៩
+
+ ឲ
+
+ ឮ
+ ឪ
+ «
+
+ ប
+ ច
+ ដ
+
+ ថ
+ ង
+ ហ
+
+
+ ក
+ ល
+ ម
+ ន
+
+ ផ
+ ឆ
+ រ
+ ស
+ ត
+
+ វ
+
+ ខ
+ យ
+ ឋ
+
+
+
+ !
+
+ "
+ ៛
+ %
+
+ (
+ )
+
+ =
+
+ ៕
+ ?
+ ៗ
+
+ ព
+ ជ
+ ឌ
+
+ ធ
+ អ
+ ះ
+
+ ញ
+ គ
+ ឡ
+
+ ណ
+
+ ភ
+ ឈ
+ ឬ
+
+ ទ
+
+
+
+ ឃ
+
+ ឍ
+
+
+
+ ឭ
+ ឧ
+ »
+
+
+
+
diff --git a/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor2.kvks.json b/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor2.kvks.json
new file mode 100644
index 00000000000..beabd9e6737
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor2.kvks.json
@@ -0,0 +1,1172 @@
+{
+ "visualkeyboard": {
+
+ "header": {
+
+ "version": "10.0",
+ "kbdname": "khmer_angkor",
+ "flags": {
+
+ "usealtgr": ""
+ }
+ },
+ "encoding": {
+
+ "$": {
+ "name": "unicode",
+ "fontname": "Khmer Busra Kbd",
+ "fontsize": "16"
+ },
+ "layer": [
+ {
+
+ "$": {
+ "shift": "RA"
+ },
+ "key": [
+ {
+ "_": "ឞ",
+ "$": {
+ "vkey": "K_B"
+ }
+ },
+ {
+ "_": "ឝ",
+ "$": {
+ "vkey": "K_K"
+ }
+ },
+ {
+ "_": "ៈ",
+ "$": {
+ "vkey": "K_QUOTE"
+ }
+ },
+ {
+ "_": "ឳ",
+ "$": {
+ "vkey": "K_RBRKT"
+ }
+ },
+ {
+ "_": "ឨ",
+ "$": {
+ "vkey": "K_T"
+ }
+ },
+ {
+ "_": "ឩ",
+ "$": {
+ "vkey": "K_LBRKT"
+ }
+ },
+ {
+ "_": "ឰ",
+ "$": {
+ "vkey": "K_P"
+ }
+ },
+ {
+ "_": "ឫ",
+ "$": {
+ "vkey": "K_R"
+ }
+ },
+ {
+ "_": "ឦ",
+ "$": {
+ "vkey": "K_I"
+ }
+ },
+ {
+ "_": "ឱ",
+ "$": {
+ "vkey": "K_O"
+ }
+ },
+ {
+ "_": "ឯ",
+ "$": {
+ "vkey": "K_E"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_3"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_W"
+ }
+ },
+ {
+ "_": "ៜ",
+ "$": {
+ "vkey": "K_Q"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_EQUAL"
+ }
+ },
+ {
+ "_": "៖",
+ "$": {
+ "vkey": "K_COLON"
+ }
+ },
+ {
+ "_": "៙",
+ "$": {
+ "vkey": "K_6"
+ }
+ },
+ {
+ "_": "៚",
+ "$": {
+ "vkey": "K_7"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_M"
+ }
+ },
+ {
+ "_": "៘",
+ "$": {
+ "vkey": "K_L"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_1"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_BKQUOTE"
+ }
+ },
+ {
+ "_": "]",
+ "$": {
+ "vkey": "K_U"
+ }
+ },
+ {
+ "_": "[",
+ "$": {
+ "vkey": "K_Y"
+ }
+ },
+ {
+ "_": "/",
+ "$": {
+ "vkey": "K_SLASH"
+ }
+ },
+ {
+ "_": ".",
+ "$": {
+ "vkey": "K_PERIOD"
+ }
+ },
+ {
+ "_": "‘",
+ "$": {
+ "vkey": "K_H"
+ }
+ },
+ {
+ "_": "+",
+ "$": {
+ "vkey": "K_A"
+ }
+ },
+ {
+ "_": "&",
+ "$": {
+ "vkey": "K_V"
+ }
+ },
+ {
+ "_": "’",
+ "$": {
+ "vkey": "K_J"
+ }
+ },
+ {
+ "_": "*",
+ "$": {
+ "vkey": "K_8"
+ }
+ },
+ {
+ "_": "@",
+ "$": {
+ "vkey": "K_2"
+ }
+ },
+ {
+ "_": "\\",
+ "$": {
+ "vkey": "K_BKSLASH"
+ }
+ },
+ {
+ "_": "}",
+ "$": {
+ "vkey": "K_0"
+ }
+ },
+ {
+ "_": "{",
+ "$": {
+ "vkey": "K_9"
+ }
+ },
+ {
+ "_": "-",
+ "$": {
+ "vkey": "K_S"
+ }
+ },
+ {
+ "_": "÷",
+ "$": {
+ "vkey": "K_F"
+ }
+ },
+ {
+ "_": ":",
+ "$": {
+ "vkey": "K_G"
+ }
+ },
+ {
+ "_": ",",
+ "$": {
+ "vkey": "K_COMMA"
+ }
+ },
+ {
+ "_": "≈",
+ "$": {
+ "vkey": "K_HYPHEN"
+ }
+ },
+ {
+ "_": ";",
+ "$": {
+ "vkey": "K_N"
+ }
+ },
+ {
+ "_": "<",
+ "$": {
+ "vkey": "K_Z"
+ }
+ },
+ {
+ "_": "#",
+ "$": {
+ "vkey": "K_C"
+ }
+ },
+ {
+ "_": ">",
+ "$": {
+ "vkey": "K_X"
+ }
+ },
+ {
+ "_": "×",
+ "$": {
+ "vkey": "K_D"
+ }
+ },
+ {
+ "_": "$",
+ "$": {
+ "vkey": "K_4"
+ }
+ },
+ {
+ "_": "€",
+ "$": {
+ "vkey": "K_5"
+ }
+ },
+ {
+ "_": " ",
+ "$": {
+ "vkey": "K_SPACE"
+ }
+ }
+ ]
+ },
+ {
+
+ "$": {
+ "shift": "SRA"
+ },
+ "key": [
+ {
+ "_": "៸",
+ "$": {
+ "vkey": "K_8"
+ }
+ },
+ {
+ "_": "៰",
+ "$": {
+ "vkey": "K_0"
+ }
+ },
+ {
+ "_": "៱",
+ "$": {
+ "vkey": "K_1"
+ }
+ },
+ {
+ "_": "៲",
+ "$": {
+ "vkey": "K_2"
+ }
+ },
+ {
+ "_": "៳",
+ "$": {
+ "vkey": "K_3"
+ }
+ },
+ {
+ "_": "៴",
+ "$": {
+ "vkey": "K_4"
+ }
+ },
+ {
+ "_": "៵",
+ "$": {
+ "vkey": "K_5"
+ }
+ },
+ {
+ "_": "៶",
+ "$": {
+ "vkey": "K_6"
+ }
+ },
+ {
+ "_": "៷",
+ "$": {
+ "vkey": "K_7"
+ }
+ },
+ {
+ "_": "៹",
+ "$": {
+ "vkey": "K_9"
+ }
+ },
+ {
+ "_": "᧿",
+ "$": {
+ "vkey": "K_PERIOD"
+ }
+ },
+ {
+ "_": "᧾",
+ "$": {
+ "vkey": "K_COMMA"
+ }
+ },
+ {
+ "_": "᧪",
+ "$": {
+ "vkey": "K_LBRKT"
+ }
+ },
+ {
+ "_": "᧫",
+ "$": {
+ "vkey": "K_RBRKT"
+ }
+ },
+ {
+ "_": "᧶",
+ "$": {
+ "vkey": "K_QUOTE"
+ }
+ },
+ {
+ "_": "᧵",
+ "$": {
+ "vkey": "K_COLON"
+ }
+ },
+ {
+ "_": "᧬",
+ "$": {
+ "vkey": "K_A"
+ }
+ },
+ {
+ "_": "᧷",
+ "$": {
+ "vkey": "K_Z"
+ }
+ },
+ {
+ "_": "᧥",
+ "$": {
+ "vkey": "K_Y"
+ }
+ },
+ {
+ "_": "᧸",
+ "$": {
+ "vkey": "K_X"
+ }
+ },
+ {
+ "_": "᧡",
+ "$": {
+ "vkey": "K_W"
+ }
+ },
+ {
+ "_": "᧻",
+ "$": {
+ "vkey": "K_B"
+ }
+ },
+ {
+ "_": "᧹",
+ "$": {
+ "vkey": "K_C"
+ }
+ },
+ {
+ "_": "᧮",
+ "$": {
+ "vkey": "K_D"
+ }
+ },
+ {
+ "_": "᧢",
+ "$": {
+ "vkey": "K_E"
+ }
+ },
+ {
+ "_": "᧯",
+ "$": {
+ "vkey": "K_F"
+ }
+ },
+ {
+ "_": "᧰",
+ "$": {
+ "vkey": "K_G"
+ }
+ },
+ {
+ "_": "᧦",
+ "$": {
+ "vkey": "K_U"
+ }
+ },
+ {
+ "_": "᧱",
+ "$": {
+ "vkey": "K_H"
+ }
+ },
+ {
+ "_": "᧤",
+ "$": {
+ "vkey": "K_T"
+ }
+ },
+ {
+ "_": "᧭",
+ "$": {
+ "vkey": "K_S"
+ }
+ },
+ {
+ "_": "᧣",
+ "$": {
+ "vkey": "K_R"
+ }
+ },
+ {
+ "_": "᧧",
+ "$": {
+ "vkey": "K_I"
+ }
+ },
+ {
+ "_": "᧠",
+ "$": {
+ "vkey": "K_Q"
+ }
+ },
+ {
+ "_": "᧺",
+ "$": {
+ "vkey": "K_V"
+ }
+ },
+ {
+ "_": "᧲",
+ "$": {
+ "vkey": "K_J"
+ }
+ },
+ {
+ "_": "᧳",
+ "$": {
+ "vkey": "K_K"
+ }
+ },
+ {
+ "_": "᧴",
+ "$": {
+ "vkey": "K_L"
+ }
+ },
+ {
+ "_": "᧩",
+ "$": {
+ "vkey": "K_P"
+ }
+ },
+ {
+ "_": "᧨",
+ "$": {
+ "vkey": "K_O"
+ }
+ },
+ {
+ "_": "᧼",
+ "$": {
+ "vkey": "K_N"
+ }
+ },
+ {
+ "_": "᧽",
+ "$": {
+ "vkey": "K_M"
+ }
+ }
+ ]
+ },
+ {
+
+ "$": {
+ "shift": ""
+ },
+ "key": [
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_SPACE"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_QUOTE"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_COMMA"
+ }
+ },
+ {
+ "_": "ឥ",
+ "$": {
+ "vkey": "K_HYPHEN"
+ }
+ },
+ {
+ "_": "។",
+ "$": {
+ "vkey": "K_PERIOD"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_SLASH"
+ }
+ },
+ {
+ "_": "០",
+ "$": {
+ "vkey": "K_0"
+ }
+ },
+ {
+ "_": "១",
+ "$": {
+ "vkey": "K_1"
+ }
+ },
+ {
+ "_": "២",
+ "$": {
+ "vkey": "K_2"
+ }
+ },
+ {
+ "_": "៣",
+ "$": {
+ "vkey": "K_3"
+ }
+ },
+ {
+ "_": "៤",
+ "$": {
+ "vkey": "K_4"
+ }
+ },
+ {
+ "_": "៥",
+ "$": {
+ "vkey": "K_5"
+ }
+ },
+ {
+ "_": "៦",
+ "$": {
+ "vkey": "K_6"
+ }
+ },
+ {
+ "_": "៧",
+ "$": {
+ "vkey": "K_7"
+ }
+ },
+ {
+ "_": "៨",
+ "$": {
+ "vkey": "K_8"
+ }
+ },
+ {
+ "_": "៩",
+ "$": {
+ "vkey": "K_9"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_COLON"
+ }
+ },
+ {
+ "_": "ឲ",
+ "$": {
+ "vkey": "K_EQUAL"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_LBRKT"
+ }
+ },
+ {
+ "_": "ឮ",
+ "$": {
+ "vkey": "K_BKSLASH"
+ }
+ },
+ {
+ "_": "ឪ",
+ "$": {
+ "vkey": "K_RBRKT"
+ }
+ },
+ {
+ "_": "«",
+ "$": {
+ "vkey": "K_BKQUOTE"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_A"
+ }
+ },
+ {
+ "_": "ប",
+ "$": {
+ "vkey": "K_B"
+ }
+ },
+ {
+ "_": "ច",
+ "$": {
+ "vkey": "K_C"
+ }
+ },
+ {
+ "_": "ដ",
+ "$": {
+ "vkey": "K_D"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_E"
+ }
+ },
+ {
+ "_": "ថ",
+ "$": {
+ "vkey": "K_F"
+ }
+ },
+ {
+ "_": "ង",
+ "$": {
+ "vkey": "K_G"
+ }
+ },
+ {
+ "_": "ហ",
+ "$": {
+ "vkey": "K_H"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_I"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_J"
+ }
+ },
+ {
+ "_": "ក",
+ "$": {
+ "vkey": "K_K"
+ }
+ },
+ {
+ "_": "ល",
+ "$": {
+ "vkey": "K_L"
+ }
+ },
+ {
+ "_": "ម",
+ "$": {
+ "vkey": "K_M"
+ }
+ },
+ {
+ "_": "ន",
+ "$": {
+ "vkey": "K_N"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_O"
+ }
+ },
+ {
+ "_": "ផ",
+ "$": {
+ "vkey": "K_P"
+ }
+ },
+ {
+ "_": "ឆ",
+ "$": {
+ "vkey": "K_Q"
+ }
+ },
+ {
+ "_": "រ",
+ "$": {
+ "vkey": "K_R"
+ }
+ },
+ {
+ "_": "ស",
+ "$": {
+ "vkey": "K_S"
+ }
+ },
+ {
+ "_": "ត",
+ "$": {
+ "vkey": "K_T"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_U"
+ }
+ },
+ {
+ "_": "វ",
+ "$": {
+ "vkey": "K_V"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_W"
+ }
+ },
+ {
+ "_": "ខ",
+ "$": {
+ "vkey": "K_X"
+ }
+ },
+ {
+ "_": "យ",
+ "$": {
+ "vkey": "K_Y"
+ }
+ },
+ {
+ "_": "ឋ",
+ "$": {
+ "vkey": "K_Z"
+ }
+ }
+ ]
+ },
+ {
+
+ "$": {
+ "shift": "S"
+ },
+ "key": [
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_SPACE"
+ }
+ },
+ {
+ "_": "!",
+ "$": {
+ "vkey": "K_1"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_QUOTE"
+ }
+ },
+ {
+ "_": "\"",
+ "$": {
+ "vkey": "K_3"
+ }
+ },
+ {
+ "_": "៛",
+ "$": {
+ "vkey": "K_4"
+ }
+ },
+ {
+ "_": "%",
+ "$": {
+ "vkey": "K_5"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_7"
+ }
+ },
+ {
+ "_": "(",
+ "$": {
+ "vkey": "K_9"
+ }
+ },
+ {
+ "_": ")",
+ "$": {
+ "vkey": "K_0"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_8"
+ }
+ },
+ {
+ "_": "=",
+ "$": {
+ "vkey": "K_EQUAL"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_COLON"
+ }
+ },
+ {
+ "_": "៕",
+ "$": {
+ "vkey": "K_PERIOD"
+ }
+ },
+ {
+ "_": "?",
+ "$": {
+ "vkey": "K_SLASH"
+ }
+ },
+ {
+ "_": "ៗ",
+ "$": {
+ "vkey": "K_2"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_A"
+ }
+ },
+ {
+ "_": "ព",
+ "$": {
+ "vkey": "K_B"
+ }
+ },
+ {
+ "_": "ជ",
+ "$": {
+ "vkey": "K_C"
+ }
+ },
+ {
+ "_": "ឌ",
+ "$": {
+ "vkey": "K_D"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_E"
+ }
+ },
+ {
+ "_": "ធ",
+ "$": {
+ "vkey": "K_F"
+ }
+ },
+ {
+ "_": "អ",
+ "$": {
+ "vkey": "K_G"
+ }
+ },
+ {
+ "_": "ះ",
+ "$": {
+ "vkey": "K_H"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_I"
+ }
+ },
+ {
+ "_": "ញ",
+ "$": {
+ "vkey": "K_J"
+ }
+ },
+ {
+ "_": "គ",
+ "$": {
+ "vkey": "K_K"
+ }
+ },
+ {
+ "_": "ឡ",
+ "$": {
+ "vkey": "K_L"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_M"
+ }
+ },
+ {
+ "_": "ណ",
+ "$": {
+ "vkey": "K_N"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_O"
+ }
+ },
+ {
+ "_": "ភ",
+ "$": {
+ "vkey": "K_P"
+ }
+ },
+ {
+ "_": "ឈ",
+ "$": {
+ "vkey": "K_Q"
+ }
+ },
+ {
+ "_": "ឬ",
+ "$": {
+ "vkey": "K_R"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_S"
+ }
+ },
+ {
+ "_": "ទ",
+ "$": {
+ "vkey": "K_T"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_U"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_V"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_W"
+ }
+ },
+ {
+ "_": "ឃ",
+ "$": {
+ "vkey": "K_X"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_Y"
+ }
+ },
+ {
+ "_": "ឍ",
+ "$": {
+ "vkey": "K_Z"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_6"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_HYPHEN"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_LBRKT"
+ }
+ },
+ {
+ "_": "ឭ",
+ "$": {
+ "vkey": "K_BKSLASH"
+ }
+ },
+ {
+ "_": "ឧ",
+ "$": {
+ "vkey": "K_RBRKT"
+ }
+ },
+ {
+ "_": "»",
+ "$": {
+ "vkey": "K_BKQUOTE"
+ }
+ },
+ {
+ "_": "",
+ "$": {
+ "vkey": "K_COMMA"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
diff --git a/developer/src/common/web/utils/test/fixtures/xml/strs_invalid-illegal.xml b/developer/src/common/web/utils/test/fixtures/xml/strs_invalid-illegal.xml
new file mode 100644
index 00000000000..3eddaffdeea
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/strs_invalid-illegal.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/developer/src/common/web/utils/test/fixtures/xml/strs_invalid-illegal.xml.json b/developer/src/common/web/utils/test/fixtures/xml/strs_invalid-illegal.xml.json
new file mode 100644
index 00000000000..e26bf18696d
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/strs_invalid-illegal.xml.json
@@ -0,0 +1,113 @@
+{
+ "keyboard3": {
+ "xmlns": "https://schemas.unicode.org/cldr/45/keyboard3",
+ "locale": "mt",
+ "conformsTo": "45",
+ "version": {
+ "number": "1.0.0"
+ },
+ "info": {
+ "author": "srl295",
+ "indicator": "🙀",
+ "layout": "qwerty",
+ "name": "TestKbd"
+ },
+ "displays": {
+ "display": [
+ {
+ "output": "a",
+ "display": "^"
+ },
+ {
+ "keyId": "e",
+ "display": "^e"
+ }
+ ],
+ "displayOptions": {
+ "baseCharacter": "e"
+ }
+ },
+ "keys": {
+ "key": [
+ {
+ "id": "hmaqtugha",
+ "output": "h\\u{FDD0}",
+ "longPressKeyIds": "a e"
+ },
+ {
+ "id": "that",
+ "output": ""
+ }
+ ]
+ },
+ "layers": {
+ "formId": "us",
+ "minDeviceWidth": "123",
+ "layer": {
+ "id": "",
+ "row": {
+ "keys": "hmaqtugha that"
+ }
+ }
+ },
+ "variables": {
+ "string": [
+ {
+ "id": "a",
+ "value": "\\m{a}"
+ },
+ {
+ "id": "vst",
+ "value": "abc pua:\\u{E010}"
+ }
+ ],
+ "set": {
+ "id": "vse",
+ "value": "a b \\u{04FFFE} \\u{5FFFF}"
+ },
+ "uset": {
+ "id": "vus",
+ "value": "[abc]"
+ }
+ },
+ "transforms": [
+ {
+ "type": "simple",
+ "transformGroup": [
+ {
+ "transform": [
+ {
+ "from": "^a",
+ "to": "\\u{FDD0}"
+ },
+ {
+ "from": "a",
+ "to": "\\m{a}\\u{E020}"
+ }
+ ]
+ },
+ {
+ "transform": {
+ "from": "\\m{a}"
+ }
+ },
+ {
+ "reorder": {
+ "before": "\\u{1A6B}",
+ "from": "\\u{1A60}[\\u{1A75}-\\u{1A79}]\\u{1A45}",
+ "order": "10 55 10"
+ }
+ }
+ ]
+ },
+ {
+ "type": "backspace",
+ "transformGroup": {
+ "transform": {
+ "from": "^e"
+ }
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/developer/src/common/web/utils/test/fixtures/xml/test_valid.kps b/developer/src/common/web/utils/test/fixtures/xml/test_valid.kps
new file mode 100644
index 00000000000..8877759a281
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/test_valid.kps
@@ -0,0 +1,46 @@
+
+
+
+ 10.0.1024.0
+ 7.0
+
+
+
+
+
+
+
+
+
+
+
+ 1.0
+ Test Valid
+
+
+
+ test_valid.kmx
+ Keyboard Test Valid
+ 0
+ .kmx
+
+
+ test_valid.js
+ Keyboard Test Valid
+ 0
+ .js
+
+
+
+
+ Test Valid
+ test_valid
+ 1.0
+ True
+
+ English
+
+
+
+
+
diff --git a/developer/src/common/web/utils/test/fixtures/xml/test_valid.kps.json b/developer/src/common/web/utils/test/fixtures/xml/test_valid.kps.json
new file mode 100644
index 00000000000..276554d09c3
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/test_valid.kps.json
@@ -0,0 +1,64 @@
+{
+ "Package": {
+ "System": {
+ "KeymanDeveloperVersion": "10.0.1024.0",
+ "FileVersion": "7.0"
+ },
+ "Options": {
+ "ExecuteProgram": "",
+ "MSIFileName": "",
+ "MSIOptions": ""
+ },
+ "StartMenu": {
+ "Folder": "",
+ "Items": ""
+ },
+ "Info": {
+ "Version": {
+ "_": "1.0",
+ "$": {
+ "URL": ""
+ }
+ },
+ "Name": {
+ "_": "Test Valid",
+ "$": {
+ "URL": ""
+ }
+ }
+ },
+ "Files": {
+ "File": [
+ {
+ "Name": "test_valid.kmx",
+ "Description": "Keyboard Test Valid",
+ "CopyLocation": "0",
+ "FileType": ".kmx"
+ },
+ {
+ "Name": "test_valid.js",
+ "Description": "Keyboard Test Valid",
+ "CopyLocation": "0",
+ "FileType": ".js"
+ }
+ ]
+ },
+ "Keyboards": {
+ "Keyboard": {
+ "Name": "Test Valid",
+ "ID": "test_valid",
+ "Version": "1.0",
+ "RTL": "True",
+ "Languages": {
+ "Language": {
+ "_": "English",
+ "$": {
+ "ID": "en"
+ }
+ }
+ }
+ }
+ },
+ "Strings": ""
+ }
+}
\ No newline at end of file
diff --git a/developer/src/common/web/utils/test/fixtures/xml/tran_fail-empty.xml b/developer/src/common/web/utils/test/fixtures/xml/tran_fail-empty.xml
new file mode 100644
index 00000000000..6c28d3f00d7
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/tran_fail-empty.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/developer/src/common/web/utils/test/fixtures/xml/tran_fail-empty.xml.json b/developer/src/common/web/utils/test/fixtures/xml/tran_fail-empty.xml.json
new file mode 100644
index 00000000000..3308970fdd8
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/tran_fail-empty.xml.json
@@ -0,0 +1,28 @@
+{
+ "keyboard3": {
+ "xmlns": "https://schemas.unicode.org/cldr/45/keyboard3",
+ "locale": "mt",
+ "conformsTo": "45",
+ "info": {
+ "name": "tran-mixed"
+ },
+ "transforms": {
+ "type": "simple",
+ "transformGroup": [
+ {
+ "transform": {
+ "from": "xx",
+ "to": "x"
+ }
+ },
+ {
+ "reorder": {
+ "from": "ខែ្ម",
+ "order": "1 3 4 2"
+ }
+ },
+ {}
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/developer/src/common/web/utils/test/fixtures/xml/tran_fail-matches-nothing-1.xml b/developer/src/common/web/utils/test/fixtures/xml/tran_fail-matches-nothing-1.xml
new file mode 100644
index 00000000000..5b5e2bba5bd
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/tran_fail-matches-nothing-1.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/developer/src/common/web/utils/test/fixtures/xml/tran_fail-matches-nothing-1.xml.json b/developer/src/common/web/utils/test/fixtures/xml/tran_fail-matches-nothing-1.xml.json
new file mode 100644
index 00000000000..477499477d8
--- /dev/null
+++ b/developer/src/common/web/utils/test/fixtures/xml/tran_fail-matches-nothing-1.xml.json
@@ -0,0 +1,19 @@
+{
+ "keyboard3": {
+ "conformsTo": "45",
+ "xmlns": "https://schemas.unicode.org/cldr/45/keyboard3",
+ "locale": "mt",
+ "info": {
+ "name": "fail-matches-nothing"
+ },
+ "keys": {},
+ "transforms": {
+ "type": "simple",
+ "transformGroup": {
+ "transform": {
+ "from": ""
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/developer/src/common/web/utils/test/kmx/test-ldml-keyboard-xml-reader.ts b/developer/src/common/web/utils/test/kmx/test-ldml-keyboard-xml-reader.ts
index a79960c7ca2..17c35660245 100644
--- a/developer/src/common/web/utils/test/kmx/test-ldml-keyboard-xml-reader.ts
+++ b/developer/src/common/web/utils/test/kmx/test-ldml-keyboard-xml-reader.ts
@@ -1,7 +1,7 @@
import { LKKey, ImportStatus } from '../../src/types/ldml-keyboard/ldml-keyboard-xml.js';
import 'mocha';
import {assert} from 'chai';
-import { CommonTypesMessages } from '../../src/common-events.js';
+import { CommonTypesMessages } from '../../src/common-messages.js';
import { testReaderCases } from '../helpers/reader-callback-test.js';
import { Constants } from '@keymanapp/common-types';
diff --git a/developer/src/common/web/utils/test/kpj/test-kpj-file-reader.ts b/developer/src/common/web/utils/test/kpj/test-kpj-file-reader.ts
index 4b154af0606..1caf70cd20d 100644
--- a/developer/src/common/web/utils/test/kpj/test-kpj-file-reader.ts
+++ b/developer/src/common/web/utils/test/kpj/test-kpj-file-reader.ts
@@ -15,9 +15,7 @@ describe('kpj-file-reader', function () {
const input = fs.readFileSync(path);
const reader = new KPJFileReader(callbacks);
const kpj = reader.read(input);
- assert.doesNotThrow(() => {
- reader.validate(kpj);
- });
+ reader.validate(kpj);
assert.equal(kpj.KeymanDeveloperProject.Options.BuildPath, '$PROJECTPATH\\build');
assert.equal(kpj.KeymanDeveloperProject.Options.CheckFilenameConventions, 'False');
assert.equal(kpj.KeymanDeveloperProject.Options.CompilerWarningsAsErrors, 'True');
diff --git a/developer/src/common/web/utils/test/test-xml-utils.ts b/developer/src/common/web/utils/test/test-xml-utils.ts
new file mode 100644
index 00000000000..1cc9724b278
--- /dev/null
+++ b/developer/src/common/web/utils/test/test-xml-utils.ts
@@ -0,0 +1,162 @@
+/*
+ * Keyman is copyright (C) SIL Global. MIT License.
+ *
+ * Created by srl on 2024-09-27
+ *
+ * Test for abstraction for XML reading and writing
+ */
+
+import { assert } from 'chai';
+import 'mocha';
+import { env } from 'node:process';
+import { readFileSync, writeFileSync } from 'node:fs';
+
+
+import { KeymanXMLType, KeymanXMLReader, KeymanXMLWriter } from '../src/xml-utils.js';
+import { makePathToFixture } from './helpers/index.js';
+
+// if true, attempt to WRITE the fixtures
+const { GEN_XML_FIXTURES } = env;
+
+class Case {
+ type: KeymanXMLType;
+ paths: string[];
+};
+
+const read_cases: Case[] = [
+ {
+ type: 'keyboard3',
+ paths: [
+ // keyboards
+ 'disp_maximal.xml',
+ 'k_020_fr.xml',
+ 'strs_invalid-illegal.xml',
+ 'tran_fail-empty.xml',
+ 'tran_fail-matches-nothing-1.xml',
+ ],
+ }, {
+ type: 'keyboardTest3',
+ paths: [
+ // keyboard test
+ 'k_020_fr-test.xml',
+ ],
+ }, {
+ type: 'kvks',
+ paths: [
+ // kvks
+ 'khmer_angkor.kvks',
+ ],
+ }, {
+ type: 'kps',
+ paths: [
+ // kps
+ 'test_valid.kps',
+ // 'error_invalid_package_file.kps',
+ ],
+ }, {
+ type: 'kpj',
+ paths: [
+ // kpj
+ 'khmer_angkor.kpj',
+ ],
+ },
+];
+
+const write_cases: Case[] = [
+ {
+ type: 'kvks',
+ paths: [
+ // kvks
+ 'khmer_angkor2.kvks', // similar to the 'read case' with the similar name, except for whitespace differences and the prologue
+ ],
+ },
+];
+
+/** read data, or null */
+function readData(path: string): string | null {
+ try {
+ return readFileSync(path, 'utf-8');
+ } catch (e) {
+ if (e?.code !== 'ENOENT') console.error(`reading ${path}`, e);
+ return null;
+ }
+}
+
+function readJson(path: string): any | null {
+ const data = readData(path);
+ if (data === null) return null;
+ return JSON.parse(data);
+}
+
+function writeJson(path: string, data: any) {
+ writeFileSync(path, JSON.stringify(data, null, ' '));
+}
+
+describe(`XML Reader Test ${GEN_XML_FIXTURES && '(update mode!)' || ''}`, () => {
+ for (const c of read_cases) {
+ const { type, paths } = c;
+ describe(`test reading ${type}`, () => {
+ for (const path of paths) {
+ const xmlPath = makePathToFixture('xml', `${path}`);
+ const jsonPath = makePathToFixture('xml', `${path}.json`);
+ it(`read: xml/${path}`, () => {
+ // get the string data
+ const xml = readData(xmlPath);
+ assert.ok(xml, `Could not read ${xmlPath}`);
+
+ const reader = new KeymanXMLReader(type);
+ assert.ok(reader);
+
+ // now, parse. subsitute endings for Win
+ const actual = reader.parse(xml.replace(/\r\n/g, '\n'));
+ assert.ok(actual, `Parser failed on ${xmlPath}`);
+
+ // get the expected
+ const expect = readJson(jsonPath);
+
+ if (GEN_XML_FIXTURES) {
+ console.log(`GEN_XML_FIXTURES: writing ${jsonPath} from actual`);
+ writeJson(jsonPath, actual);
+ } else {
+ assert.ok(expect, `Could not read ${jsonPath} - run with env GEN_XML_FIXTURES=1 to update.`);
+ assert.deepEqual(actual, expect, `Mismatch of ${xmlPath} vs ${jsonPath}`);
+ }
+ });
+ }
+ });
+ }
+});
+
+
+describe(`XML Writer Test ${GEN_XML_FIXTURES && '(update mode!)' || ''}`, () => {
+ for (const c of write_cases) {
+ const { type, paths } = c;
+ describe(`test writing ${type}`, () => {
+ const writer = new KeymanXMLWriter(type);
+ assert.ok(writer);
+ for (const path of paths) {
+ const jsonPath = makePathToFixture('xml', `${path}.json`);
+ const xmlPath = makePathToFixture('xml', `${path}`);
+ it(`write: xml/${path}`, () => {
+ // get the object data
+ const data = readJson(jsonPath);
+ assert.ok(data, `Could not read input ${jsonPath}`);
+
+ // now, write.
+ const actual = writer.write(data);
+ assert.ok(actual, `Writer failed on ${jsonPath}`);
+
+ if (GEN_XML_FIXTURES) {
+ console.log(`GEN_XML_FIXTURES: writing ${xmlPath} from actual`);
+ writeFileSync(xmlPath, actual);
+ } else {
+ // get the expected data
+ const expect = readData(xmlPath).replace(/\r\n/g, '\n');
+ assert.ok(expect, `Could not read expected output ${xmlPath} - run with env=GEN_XML_FIXTURES=1 to update`);
+ assert.deepEqual(actual.trim(), expect.trim(), `Mismatch of ${xmlPath} vs ${jsonPath}`);
+ }
+ });
+ }
+ });
+ }
+});
diff --git a/developer/src/common/web/utils/tsconfig.json b/developer/src/common/web/utils/tsconfig.json
index 3be972dbcb6..6f96f75ee70 100644
--- a/developer/src/common/web/utils/tsconfig.json
+++ b/developer/src/common/web/utils/tsconfig.json
@@ -8,6 +8,5 @@
},
"include": [
"src/**/*.ts",
- "src/deps/xml2js/*.js",
],
-}
\ No newline at end of file
+}
diff --git a/developer/src/kmc-analyze/.eslintrc.cjs b/developer/src/kmc-analyze/.eslintrc.cjs
index bd0835f0be3..148ad7579f8 100644
--- a/developer/src/kmc-analyze/.eslintrc.cjs
+++ b/developer/src/kmc-analyze/.eslintrc.cjs
@@ -5,7 +5,7 @@ module.exports = {
overrides: [
{
files:"src/**/*.ts",
- extends: ["../../../common/web/eslint/eslintNoNodeImports.js"],
+ extends: ["../../../common/tools/eslint/eslintNoNodeImports.js"],
}
],
rules: {
diff --git a/developer/src/kmc-analyze/src/analyzer-messages.ts b/developer/src/kmc-analyze/src/analyzer-messages.ts
index 1e4204595ed..c895a2f3675 100644
--- a/developer/src/kmc-analyze/src/analyzer-messages.ts
+++ b/developer/src/kmc-analyze/src/analyzer-messages.ts
@@ -1,6 +1,3 @@
-///
-// above is required for now, seemingly https://github.com/microsoft/TypeScript/issues/42873
-// probably addressed in ts 5.5.2
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def, CompilerMessageSpecWithException, KeymanUrls } from "@keymanapp/developer-utils";
const Namespace = CompilerErrorNamespace.Analyzer;
diff --git a/developer/src/kmc-kmn/.eslintrc.cjs b/developer/src/kmc-kmn/.eslintrc.cjs
index 156e7b5cf07..9fb322a1e46 100644
--- a/developer/src/kmc-kmn/.eslintrc.cjs
+++ b/developer/src/kmc-kmn/.eslintrc.cjs
@@ -5,7 +5,7 @@ module.exports = {
overrides: [
{
files:"src/**/*.ts",
- extends: ["../../../common/web/eslint/eslintNoNodeImports.js"],
+ extends: ["../../../common/tools/eslint/eslintNoNodeImports.js"],
}
],
ignorePatterns: ["test/fixtures/*"],
diff --git a/developer/src/kmc-ldml/.eslintrc.cjs b/developer/src/kmc-ldml/.eslintrc.cjs
index 09038ae9291..41a487fe41d 100644
--- a/developer/src/kmc-ldml/.eslintrc.cjs
+++ b/developer/src/kmc-ldml/.eslintrc.cjs
@@ -6,7 +6,7 @@ module.exports = {
overrides: [
{
files:"src/**/*.ts",
- extends: ["../../../common/web/eslint/eslintNoNodeImports.js"],
+ extends: ["../../../common/tools/eslint/eslintNoNodeImports.js"],
}
],
rules: {
diff --git a/developer/src/kmc-ldml/package.json b/developer/src/kmc-ldml/package.json
index 6f65eaf0e0a..11d7c07b880 100644
--- a/developer/src/kmc-ldml/package.json
+++ b/developer/src/kmc-ldml/package.json
@@ -25,8 +25,8 @@
"url": "https://github.com/keymanapp/keyman/issues"
},
"dependencies": {
- "@keymanapp/keyman-version": "*",
"@keymanapp/developer-utils": "*",
+ "@keymanapp/keyman-version": "*",
"@keymanapp/kmc-kmn": "*",
"@keymanapp/ldml-keyboard-constants": "*",
"semver": "^7.5.4"
@@ -34,11 +34,13 @@
"devDependencies": {
"@keymanapp/developer-test-helpers": "*",
"@keymanapp/resources-gosh": "*",
+ "@types/common-tags": "^1.8.4",
"@types/mocha": "^5.2.7",
"@types/node": "^20.4.1",
"@types/semver": "^7.3.12",
"c8": "^7.12.0",
"chalk": "^2.4.2",
+ "common-tags": "^1.8.2",
"mocha": "^8.4.0",
"typescript": "^5.4.5"
},
diff --git a/developer/src/kmc-ldml/src/compiler/compiler.ts b/developer/src/kmc-ldml/src/compiler/compiler.ts
index 950534e69c8..388a64de69b 100644
--- a/developer/src/kmc-ldml/src/compiler/compiler.ts
+++ b/developer/src/kmc-ldml/src/compiler/compiler.ts
@@ -1,4 +1,9 @@
-import { KMXPlus, UnicodeSetParser, KvkFileWriter } from '@keymanapp/common-types';
+/*
+ * Keyman is copyright (C) SIL International. MIT License.
+ *
+ * Compiles a LDML XML keyboard file into a Keyman KMXPlus file
+ */
+import { KMXPlus, UnicodeSetParser, KvkFileWriter, KMX } from '@keymanapp/common-types';
import {
CompilerCallbacks, KeymanCompiler, KeymanCompilerResult, KeymanCompilerArtifacts,
defaultCompilerOptions, LDMLKeyboardXMLSourceFileReader, LDMLKeyboard,
@@ -25,7 +30,6 @@ import { KmnCompiler } from '@keymanapp/kmc-kmn';
import { KMXPlusMetadataCompiler } from './metadata-compiler.js';
import { LdmlKeyboardVisualKeyboardCompiler } from './visual-keyboard-compiler.js';
import { LinterKeycaps } from './linter-keycaps.js';
-//KMW17.0: import { LdmlKeyboardKeymanWebCompiler } from './keymanweb-compiler.js';
export const SECTION_COMPILERS = [
// These are in dependency order.
@@ -68,11 +72,6 @@ export interface LdmlKeyboardCompilerArtifacts extends KeymanCompilerArtifacts {
* desktop projects alongside .kmx
*/
kvk?: KeymanCompilerArtifactOptional;
- /**
- * Javascript keyboard filedata and filename - installable into KeymanWeb,
- * Keyman mobile products
- */
- js?: KeymanCompilerArtifactOptional;
};
export interface LdmlKeyboardCompilerResult extends KeymanCompilerResult {
@@ -85,7 +84,7 @@ export interface LdmlKeyboardCompilerResult extends KeymanCompilerResult {
/**
* @public
- * Compiles a LDML keyboard .xml file to a .kmx (KMXPlus), .kvk, and/or .js. The
+ * Compiles a LDML keyboard .xml file to a .kmx (KMXPlus), .kvk. The
* compiler does not read or write from filesystem or network directly, but
* relies on callbacks for all external IO.
*/
@@ -111,7 +110,7 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
}
/**
- * Compiles a LDML keyboard .xml file to .kmx, .kvk, and/or .js files. Returns
+ * Compiles a LDML keyboard .xml file to .kmx, .kvk files. Returns
* an object containing binary artifacts on success. The files are passed in
* by name, and the compiler will use callbacks as passed to the
* {@link LdmlKeyboardCompiler.init} function to read any input files by disk.
@@ -137,18 +136,34 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
return null;
}
+ outputFilename = outputFilename ?? inputFilename.replace(/\.xml$/, '.kmx');
+
// In order for the KMX file to be loaded by non-KMXPlus components, it is helpful
// to duplicate some of the metadata
KMXPlusMetadataCompiler.addKmxMetadata(kmx.kmxplus, kmx.keyboard, compilerOptions);
// Use the builder to generate the binary output file
- const builder = new KMXBuilder(kmx, compilerOptions.saveDebug);
- const kmx_binary = builder.compile();
+ const kmxBuilder = new KMXBuilder(kmx, compilerOptions.saveDebug);
+ const keyboardId = this.callbacks.path.basename(outputFilename, '.kmx');
+ const vkCompiler = new LdmlKeyboardVisualKeyboardCompiler(this.callbacks);
+ const vkCompilerResult = vkCompiler.compile(kmx.kmxplus, keyboardId);
+ if(vkCompilerResult === null) {
+ return null;
+ }
+ const vkData = typeof vkCompilerResult == 'object' ? vkCompilerResult : null;
- const vkcompiler = new LdmlKeyboardVisualKeyboardCompiler(this.callbacks);
- const vk = vkcompiler.compile(source);
- const writer = new KvkFileWriter();
- const kvk_binary = writer.write(vk);
+ if(vkData) {
+ kmx.keyboard.stores.push({
+ dpName: '',
+ dpString: keyboardId + '.kvk',
+ dwSystemID: KMX.KMXFile.TSS_VISUALKEYBOARD
+ });
+ }
+
+ const kmxBinary = kmxBuilder.compile();
+
+ const kvkWriter = new KvkFileWriter();
+ const kvkBinary = vkData ? kvkWriter.write(vkData) : null;
// Note: we could have a step of generating source files here
// KvksFileWriter()...
@@ -161,13 +176,10 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
//KMW17.0: const encoder = new TextEncoder();
//KMW17.0: const kmw_binary = encoder.encode(kmw_string);
- outputFilename = outputFilename ?? inputFilename.replace(/\.xml$/, '.kmx');
-
return {
artifacts: {
- kmx: { data: kmx_binary, filename: outputFilename },
- kvk: { data: kvk_binary, filename: outputFilename.replace(/\.kmx$/, '.kvk') },
- //KMW17.0: js: { data: kmw_binary, filename: outputFilename.replace(/\.kmx$/, '.js') },
+ kmx: { data: kmxBinary, filename: outputFilename },
+ kvk: kvkBinary ? { data: kvkBinary, filename: outputFilename.replace(/\.kmx$/, '.kvk') } : null,
}
};
}
@@ -178,7 +190,6 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
*
* - .kmx file - binary keyboard used by Keyman on desktop platforms
* - .kvk file - binary on screen keyboard used by Keyman on desktop platforms
- * - .js file - Javascript keyboard for web and touch platforms
*
* @param artifacts - object containing artifact binary data to write out
* @returns true on success
@@ -192,10 +203,6 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
this.callbacks.fs.writeFileSync(artifacts.kvk.filename, artifacts.kvk.data);
}
- if (artifacts.js) {
- this.callbacks.fs.writeFileSync(artifacts.js.filename, artifacts.js.data);
- }
-
return true;
}
diff --git a/developer/src/kmc-ldml/src/compiler/empty-compiler.ts b/developer/src/kmc-ldml/src/compiler/empty-compiler.ts
index 92c364e4dc9..44b04182a3f 100644
--- a/developer/src/kmc-ldml/src/compiler/empty-compiler.ts
+++ b/developer/src/kmc-ldml/src/compiler/empty-compiler.ts
@@ -6,7 +6,7 @@ import { VarsCompiler } from './vars.js';
import { LdmlCompilerMessages } from './ldml-compiler-messages.js';
/**
- * Compiler for typrs that don't actually consume input XML
+ * Compiler for types that don't actually consume input XML
*/
export abstract class EmptyCompiler extends SectionCompiler {
private _id: SectionIdent;
diff --git a/developer/src/kmc-ldml/src/compiler/keymanweb-compiler.ts b/developer/src/kmc-ldml/src/compiler/keymanweb-compiler.ts
deleted file mode 100644
index 86402b6d45e..00000000000
--- a/developer/src/kmc-ldml/src/compiler/keymanweb-compiler.ts
+++ /dev/null
@@ -1,114 +0,0 @@
-import { VisualKeyboard, KeymanFileTypes } from "@keymanapp/common-types";
-import { CompilerCallbacks, LDMLKeyboard, TouchLayoutFileWriter } from "@keymanapp/developer-utils";
-import { LdmlCompilerOptions } from "./ldml-compiler-options.js";
-import { TouchLayoutCompiler } from "./touch-layout-compiler.js";
-import { LdmlKeyboardVisualKeyboardCompiler } from "./visual-keyboard-compiler.js";
-
-const MINIMUM_KMW_VERSION = '16.0';
-
-export class LdmlKeyboardKeymanWebCompiler {
- private readonly options: LdmlCompilerOptions;
- private readonly nl: string;
- private readonly tab: string;
- constructor(private callbacks: CompilerCallbacks, options?: LdmlCompilerOptions) {
- this.options = { ...options };
- this.nl = this.options.saveDebug ? "\n" : '';
- this.tab = this.options.saveDebug ? " " : '';
- }
-
- public compileVisualKeyboard(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile) {
- const nl = this.nl, tab = this.tab;
- const vkc = new LdmlKeyboardVisualKeyboardCompiler(this.callbacks);
- const vk: VisualKeyboard.VisualKeyboard = vkc.compile(source);
-
- let result =
- `{F: ' 1em ${JSON.stringify(vk.header.unicodeFont.name)}', `+
- `K102: ${vk.header.flags & VisualKeyboard.VisualKeyboardHeaderFlags.kvkh102 ? 1 : 0}};${nl}` + // TODO-LDML: escape ' and " in font name correctly
- `${tab}this.KV.KLS={${nl}` +
- `${tab}${tab}TODO_LDML: ${vk.keys.length}${nl}` +
- // TODO-LDML: fill in KLS
- `${tab}}`;
-
- return result;
- }
-
- public compileTouchLayout(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile) {
- const tlcompiler = new TouchLayoutCompiler();
- const layout = tlcompiler.compileToJavascript(source);
- const writer = new TouchLayoutFileWriter({formatted: this.options.saveDebug});
- return writer.compile(layout);
- }
-
- private cleanName(name: string): string {
- let result = this.callbacks.path.basename(name, KeymanFileTypes.Source.LdmlKeyboard);
- if(!result) {
- throw new Error(`Invalid file name ${name}`);
- }
-
- result = result.replaceAll(/[^a-z0-9]/g, '_');
- if(result.match(/^[0-9]/)) {
- // Can't have a digit as initial
- result = '_' + result;
- }
- return result;
- }
-
- public compile(name: string, source: LDMLKeyboard.LDMLKeyboardXMLSourceFile): string {
- const nl = this.nl, tab = this.tab;
-
- const sName = 'Keyboard_'+this.cleanName(name);
- const displayUnderlying = true; // TODO-LDML
- const modifierBitmask = 0; // TODO-LDML: define the modifiers used by this keyboard
- const vkDictionary = ''; // TODO-LDML: vk dictionary for touch keys
- const hasSupplementaryPlaneChars = false; // TODO-LDML
- const isRTL = false; // TODO-LDML
-
- let result =
- `if(typeof keyman === 'undefined') {${nl}` +
- `${tab}console.error('Keyboard requires KeymanWeb ${MINIMUM_KMW_VERSION} or later');${nl}` +
- `} else {${nl}` +
- `${tab}KeymanWeb.KR(new ${sName}());${nl}` +
- `}${nl}` +
- `function ${sName}() {${nl}` +
- // `${tab}${this.setupDebug()}${nl}` + ? we may use this for modifierBitmask in future
- // `${tab}this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;${nl}` + ? we probably don't need this, it's for back-compat
- `${tab}this.KI="${sName}";${nl}` +
- `${tab}this.KN=${JSON.stringify(source.keyboard3.info.name)};${nl}` +
- `${tab}this.KMINVER=${JSON.stringify(MINIMUM_KMW_VERSION)};${nl}` +
- `${tab}this.KV=${this.compileVisualKeyboard(source)};${nl}` +
- `${tab}this.KDU=${displayUnderlying ? '1' : '0'};${nl}` +
- `${tab}this.KH="";${nl}` + // TODO-LDML: help text not supported
- `${tab}this.KM=0;${nl}` + // TODO-LDML: mnemonic layout not supported for LDML keyboards
- `${tab}this.KBVER=${JSON.stringify(source.keyboard3.version?.number || '0.0')};${nl}` +
- `${tab}this.KMBM=${modifierBitmask};${nl}`;
-
- if(isRTL) {
- result += `${tab}this.KRTL=1;${nl}`;
- }
-
- if(hasSupplementaryPlaneChars) {
- result += `${tab}this.KS=1;${nl}`;
- }
-
- if(vkDictionary != '') {
- result += `${tab}this.KVKD=${JSON.stringify(vkDictionary)};${nl}`;
- }
-
- let layoutFile = this.compileTouchLayout(source);
- if(layoutFile != '') {
- result += `${tab}this.KVKL=${layoutFile};${nl}`;
- }
- // TODO-LDML: KCSS not supported
-
- // TODO-LDML: embed binary keyboard for loading into Core
-
- // A LDML keyboard has a no-op for its gs() (begin Unicode) function,
- // because the functionality is embedded in Keyman Core
- result += `${tab}this.gs=function(t,e){${nl}`+
- `${tab}${tab}return 0;${nl}`+ // TODO-LDML: we will start by embedding call into Keyman Core here
- `${tab}};${nl}`;
-
- result += `}${nl}`;
- return result;
- }
-}
diff --git a/developer/src/kmc-ldml/src/compiler/layr.ts b/developer/src/kmc-ldml/src/compiler/layr.ts
index 62f8b3360d0..6c945a18642 100644
--- a/developer/src/kmc-ldml/src/compiler/layr.ts
+++ b/developer/src/kmc-ldml/src/compiler/layr.ts
@@ -63,7 +63,7 @@ export class LayrCompiler extends SectionCompiler {
for (const layer of layers.layer) {
const rows = layer.row.map((row) => {
const erow: LayrRow = {
- keys: row.keys.split(' ').map((id) => sections.strs.allocString(id)),
+ keys: row.keys.trim().split(/[ \t]+/).map((id) => sections.strs.allocString(id)),
};
return erow;
});
diff --git a/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts b/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts
index 1b6752bdee8..901f4b00227 100644
--- a/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts
+++ b/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts
@@ -187,7 +187,13 @@ export class LdmlCompilerMessages {
static Error_UnparseableReorderSet = (o: { from: string, set: string }) =>
m(this.ERROR_UnparseableReorderSet, `Illegal UnicodeSet "${def(o.set)}" in reorder "${def(o.from)}`);
- // Available: 0x029
+ static ERROR_InvalidVariableIdentifer = SevError | 0x0029;
+ static Error_InvalidVariableIdentifer = (o: { id: string }) => m(
+ this.ERROR_InvalidVariableIdentifer,
+ `Invalid variable identifier "\\u${def(o.id)}". Identifiers must be between 1 and 32 characters, and can use A-Z, a-z, 0-9, and _.`,
+ );
+
+ // Available: 0x02A-0x2F
static ERROR_InvalidQuadEscape = SevError | 0x0030;
static Error_InvalidQuadEscape = (o: { cp: number }) =>
@@ -202,9 +208,12 @@ export class LdmlCompilerMessages {
m(this.ERROR_UnparseableTransformFrom, `Invalid transform from="${def(o.from)}": "${def(o.message)}"`);
static ERROR_IllegalTransformDollarsign = SevErrorTransform | 0x01;
- static Error_IllegalTransformDollarsign = (o: { from: string }) =>
- m(this.ERROR_IllegalTransformDollarsign, `Invalid transform from="${def(o.from)}": Unescaped dollar-sign ($) is not valid transform syntax.`,
- '**Hint**: Use `\\$` to match a literal dollar-sign.');
+ static Error_IllegalTransformDollarsign = (o: { from: string }) => m(
+ this.ERROR_IllegalTransformDollarsign,
+ `Invalid transform from="${def(o.from)}": Unescaped dollar-sign ($) is not valid transform syntax.`,
+ `**Hint**: Use \`\\$\` to match a literal dollar-sign. If this precedes a variable name, `+
+ `the variable name may not be valid (A-Z, a-z, 0-9, _, 32 character maximum).`
+ );
static ERROR_TransformFromMatchesNothing = SevErrorTransform | 0x02;
static Error_TransformFromMatchesNothing = (o: { from: string }) =>
diff --git a/developer/src/kmc-ldml/src/compiler/touch-layout-compiler.ts b/developer/src/kmc-ldml/src/compiler/touch-layout-compiler.ts
deleted file mode 100644
index a36ab9c0974..00000000000
--- a/developer/src/kmc-ldml/src/compiler/touch-layout-compiler.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-import { TouchLayout } from "@keymanapp/common-types";
-import { LDMLKeyboard } from "@keymanapp/developer-utils";
-
-export class TouchLayoutCompiler {
- public compileToJavascript(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile): TouchLayout.TouchLayoutFile {
- let result: TouchLayout.TouchLayoutFile = {};
-
- // start with desktop to mimic vk emit
- result.desktop = {
- defaultHint: "none", // TODO-LDML this should be optional
- layer: []
- };
-
- for(let layers of source.keyboard3.layers) {
- for(let layer of layers.layer) {
- const resultLayer = this.compileHardwareLayer(source, result, layer);
- result.desktop.layer.push(resultLayer);
- }
- }
- return result;
- }
-
- private compileHardwareLayer(
- source: LDMLKeyboard.LDMLKeyboardXMLSourceFile,
- file: TouchLayout.TouchLayoutFile,
- layer: LDMLKeyboard.LKLayer
- ) {
- // TODO-LDML: consider consolidation with keys.ts?
-
- let fileLayer: TouchLayout.TouchLayoutLayer = {
- id: this.translateLayerIdToTouchLayoutLayerId(layer.id, layer.modifiers),
- row: []
- };
-
- let y = -1;
-
- for(let row of layer.row) {
- y++;
-
- let fileRow: TouchLayout.TouchLayoutRow = {id: y, key: []};
- fileLayer.row.push(fileRow);
-
- const keys = row.keys.split(' ');
- for(let key of keys) {
- const keydef = source.keyboard3.keys?.key?.find(x => x.id == key);
- if(keydef) {
- const fileKey: TouchLayout.TouchLayoutKey = {
- id: this.translateKeyIdentifierToTouch(keydef.id) as TouchLayout.TouchLayoutKeyId,
- text: keydef.output || '',
- // TODO-LDML: additional properties
- };
- fileRow.key.push(fileKey);
- } else {
- // TODO-LDML: consider logging missing keys
- }
- }
- }
-
- return fileLayer;
- }
-
- private translateLayerIdToTouchLayoutLayerId(id: string, modifier: string): string {
- // Touch layout layers have a set of reserved names that correspond to
- // hardware modifiers. We want to map these identifiers first before falling
- // back to the layer ids
-
- // The set of recognized layer identifiers is:
- //
- // touch | LDML
- // ---------------+-------------
- // default | none
- // shift | shift
- // caps | caps
- // rightalt | altR
- // rightalt-shift | altR shift
- //
- const map = {
- none: 'default',
- shift: 'shift',
- caps: 'caps',
- altR: 'rightalt',
- "altR shift": 'rightalt-shift'
- };
-
- // canonicalize modifier string, alphabetical
- // TODO-LDML: need to support multiple here
- if (modifier && modifier.indexOf(',') !== -1) {
- throw Error(`Internal error: TODO-LDML: multiple modifiers ${modifier} not yet supported.`);
- }
- modifier = (modifier||'').split(/\b/).sort().join(' ').trim();
-
- if(Object.hasOwn(map, modifier)) {
- return (map as any)[modifier];
- }
-
- // TODO-LDML: Other layer names will be used unmodified, is this sufficient?
- return id;
- }
-
- private translateKeyIdentifierToTouch(id: string): string {
- // Note: keys identifiers in kmx were traditionally case-insensitive, but we
- // are going to use them as case-insensitive for LDML keyboards. The set of
- // standard key identifiers a-z, A-Z, 0-9 will be used, where possible, and
- // all other keys will be mapped to `T_key`.
-
- if(id.match(/^[0-9a-zA-Z]$/)) {
- return 'K_'+id;
- }
-
- // Not a standard key
- return 'T_'+id;
- }
-}
diff --git a/developer/src/kmc-ldml/src/compiler/vars.ts b/developer/src/kmc-ldml/src/compiler/vars.ts
index fb9b2f75965..3fc39f0d011 100644
--- a/developer/src/kmc-ldml/src/compiler/vars.ts
+++ b/developer/src/kmc-ldml/src/compiler/vars.ts
@@ -40,6 +40,14 @@ export class VarsCompiler extends SectionCompiler {
return valid;
}
+ private validateIdentifier(id: string) {
+ if(!id.match(VariableParser.ID)) { // From DTD
+ this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidVariableIdentifer({id}));
+ return false;
+ }
+ return true;
+ }
+
private validateVars(st: Substitutions): boolean {
let valid = true;
const variables = this.keyboard3?.variables;
@@ -67,13 +75,28 @@ export class VarsCompiler extends SectionCompiler {
if (variables) {
// Strings
for (const { id, value } of variables.string) {
+ if(!this.validateIdentifier(id)) {
+ valid = false;
+ continue;
+ }
addId(id);
- allStrings.add(id);
const stringrefs = VariableParser.allStringReferences(value);
+ for(const ref of stringrefs) {
+ if(!allStrings.has(ref)) {
+ valid = false;
+ this.callbacks.reportMessage(LdmlCompilerMessages.Error_MissingStringVariable({id: ref}));
+ allStrings.add(ref); // avoids multiple reports of same missing variable
+ }
+ }
st.string.add(SubstitutionUse.variable, stringrefs);
+ allStrings.add(id);
}
// Sets
for (const { id, value } of variables.set) {
+ if(!this.validateIdentifier(id)) {
+ valid = false;
+ continue;
+ }
addId(id);
allSets.add(id);
// check for illegal references, here.
@@ -96,6 +119,10 @@ export class VarsCompiler extends SectionCompiler {
}
// UnicodeSets
for (const { id, value } of variables.uset) {
+ if(!this.validateIdentifier(id)) {
+ valid = false;
+ continue;
+ }
addId(id);
allUnicodeSets.add(id);
const stringrefs = VariableParser.allStringReferences(value);
diff --git a/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts b/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts
index 6d76bc403ea..7783d42405c 100644
--- a/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts
+++ b/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts
@@ -1,6 +1,12 @@
-import { VisualKeyboard } from "@keymanapp/common-types";
-import { LDMLKeyboard, CompilerCallbacks } from "@keymanapp/developer-utils";
-import { KeysCompiler } from "./keys.js";
+/*
+ * Keyman is copyright (C) SIL International. MIT License.
+ *
+ * Export LDML data (https://www.unicode.org/reports/tr35/tr35-keyboards.html)
+ * to .kvk format. This is an interim solution until Keyman Core supports
+ * interrogation of the KMX+ data for OSK.
+ */
+import { ModifierKeyConstants, KMXPlus, VisualKeyboard } from "@keymanapp/common-types";
+import { CompilerCallbacks } from "@keymanapp/developer-utils";
import { LdmlCompilerMessages } from "./ldml-compiler-messages.js";
// This is a partial polyfill for findLast, so not polluting Array.prototype
@@ -20,84 +26,139 @@ function findLast(arr: any, callback: any) {
return undefined;
}
+
+const LDML_MODIFIER_TO_KVK_MODIFIER = new Map();
+LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.LCTRLFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_LCTRL);
+LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.RCTRLFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_RCTRL);
+LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.LALTFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_LALT);
+LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.RALTFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_RALT);
+LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.K_SHIFTFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_SHIFT);
+LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.K_CTRLFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_CTRL);
+LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.K_ALTFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_ALT);
+
export class LdmlKeyboardVisualKeyboardCompiler {
public constructor(private callbacks: CompilerCallbacks) {
}
- public compile(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile): VisualKeyboard.VisualKeyboard {
+ /**
+ * Generate a visual keyboard
+ * @param source Compiled KMX+ data; note that this is modified to add
+ * &VISUALKEYBOARD system store on success
+ * @param keyboardId Basename of keyboard, without file extension
+ * @returns Visual keyboard data on success, null on failure, or
+ * false if no VK was generated for this keyboard
+ */
+ public compile(source: KMXPlus.KMXPlusData, keyboardId: string): VisualKeyboard.VisualKeyboard | boolean | null {
let result = new VisualKeyboard.VisualKeyboard();
/* TODO-LDML: consider VisualKeyboardHeaderFlags.kvkhUseUnderlying kvkhDisplayUnderlying kvkhAltGr kvkh102 */
result.header.flags = 0;
result.header.version = 0x0600;
-
- /* TODO-LDML: consider associatedKeyboard: this _must_ be set to id (aka basename sans ext) of keyboard .kmx file */
- result.header.associatedKeyboard = '';
+ result.header.associatedKeyboard = keyboardId;
result.header.ansiFont = {...VisualKeyboard.DEFAULT_KVK_FONT};
result.header.unicodeFont = {...VisualKeyboard.DEFAULT_KVK_FONT};
- for(let layers of source.keyboard3.layers) {
- const { formId } = layers;
- for(let layer of layers.layer) {
- this.compileHardwareLayer(source, result, layer, formId);
+ let hasVisualKeyboard = false;
+
+ for(let layersList of source.layr.lists) {
+ const formId = layersList.hardware.value;
+ if(formId == 'touch') {
+ continue;
}
+
+ for(let layer of layersList.layers) {
+ const res = this.compileHardwareLayer(source, result, layer, formId);
+ if(res === false) {
+ // failed to compile the layer
+ return null;
+ }
+ if(res === null) {
+ // not a supported layer type, but not an error
+ continue;
+ }
+ hasVisualKeyboard = true;
+ }
+ }
+
+ if(!hasVisualKeyboard) {
+ return false;
}
+
return result;
}
private compileHardwareLayer(
- source: LDMLKeyboard.LDMLKeyboardXMLSourceFile,
+ source: KMXPlus.KMXPlusData,
vk: VisualKeyboard.VisualKeyboard,
- layer: LDMLKeyboard.LKLayer,
+ layer: KMXPlus.LayrEntry,
hardware: string,
) {
- const layerId = layer.id;
- if (hardware === 'touch') {
- hardware = 'us'; // TODO-LDML: US Only. Do something different here?
- }
- const keymap = KeysCompiler.getKeymapFromForms(source.keyboard3?.forms?.form, hardware);
- if (!keymap) {
- this.callbacks.reportMessage(
- LdmlCompilerMessages.Error_InvalidHardware({ formId: hardware })
- );
- return;
+ const layerId = layer.id.value;
+
+ hardware = 'us'; // TODO-LDML: US Only. We need to clean this up for other hardware forms
+
+ const shift = this.translateLayerModifiersToVisualKeyboardShift(layer.mod);
+ if(shift === null) {
+ // Caps (num, scroll) is not a supported shift state in .kvk
+ return null;
}
- const shift = this.translateLayerIdToVisualKeyboardShift(layer.id);
+ let result = true;
let y = -1;
- for(let row of layer.row) {
+ for(let row of layer.rows) {
y++;
-
- const keys = row.keys.split(' ');
let x = -1;
- for(let key of keys) {
- const keyId = key;
+ for(let key of row.keys) {
x++;
- //@ts-ignore
- let keydef = findLast(source.keyboard3.keys?.key, x => x.id == key);
+ const keydef: KMXPlus.KeysKeys = findLast(source.keys?.keys, (kd: KMXPlus.KeysKeys) => kd.id.value == key.value);
+ const kmap = source.keys.kmap.find(k => k.key == keydef.id.value && k.mod == layer.mod);
+ const text = this.getDisplayFromKey(keydef, source) ?? null;
- if (!keydef) {
+ if (!keydef || !kmap || text === null) {
this.callbacks.reportMessage(
- LdmlCompilerMessages.Error_KeyNotFoundInKeyBag({ keyId, layer: layerId, row: y, col: x, form: hardware })
+ LdmlCompilerMessages.Error_KeyNotFoundInKeyBag({ keyId: key.value, layer: layerId, row: y, col: x, form: hardware })
);
+ result = false;
} else {
vk.keys.push({
flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode,
- shift: shift,
- text: keydef.output, // TODO-LDML: displays
- vkey: keymap[y][x],
+ shift,
+ text,
+ vkey: kmap.vkey
});
}
}
}
+ return result;
}
- private translateLayerIdToVisualKeyboardShift(id: string) {
- if(id == 'base') {
- return 0;
+ private getDisplayFromKey(keydef: KMXPlus.KeysKeys, source: KMXPlus.KMXPlusData) {
+ const display = source.disp?.disps?.find(d => d.id.value == keydef.id.value || d.to.value == keydef.to.value);
+ return display?.display.value ?? keydef.to.value;
+ }
+
+ private translateLayerModifiersToVisualKeyboardShift(modifiers: number): VisualKeyboard.VisualKeyboardShiftState {
+
+ if(modifiers == 0) {
+ return VisualKeyboard.VisualKeyboardShiftState.KVKS_NORMAL;
}
- // TODO-LDML: other modifiers
- return 0;
+
+ if(modifiers &
+ (ModifierKeyConstants.CAPITALFLAG | ModifierKeyConstants.NUMLOCKFLAG | ModifierKeyConstants.SCROLLFLAG)
+ ) {
+ // Caps/Num/Scroll are not supported in .kvk, in combination or alone
+ return null;
+ }
+
+ let shift: VisualKeyboard.VisualKeyboardShiftState = 0;
+
+ for(const mod of LDML_MODIFIER_TO_KVK_MODIFIER.keys()) {
+ if(modifiers & mod) {
+ shift |= LDML_MODIFIER_TO_KVK_MODIFIER.get(mod);
+ }
+ }
+
+ return shift;
}
}
diff --git a/developer/src/kmc-ldml/test/fixtures/basic-kvk.txt b/developer/src/kmc-ldml/test/fixtures/basic-kvk.txt
index 68666feacf3..da8f5e6011a 100644
--- a/developer/src/kmc-ldml/test/fixtures/basic-kvk.txt
+++ b/developer/src/kmc-ldml/test/fixtures/basic-kvk.txt
@@ -1,4 +1,6 @@
#
+# Keyman is copyright (C) SIL International. MIT License.
+#
# basic-kvk.txt describes the expected output of running kmc against basic.xml to generate
# a .kvk file. It is used in the end-to-end test test-visual-keyboard-compiler-e2e.ts.
#
@@ -10,8 +12,8 @@ block(kvkheader)
00 06 00 00 # version 0x0600
00 # flags
block(associated_keyboard)
- 01 00 # string len in UTF-16 code units incl zterm
- 00 00 # '\0'
+ 06 00 # string len in UTF-16 code units incl zterm
+ 62 00 61 00 73 00 69 00 63 00 00 00 # 'basic\0'
block(ansi_font)
06 00 # string len in UTF-16 code units incl zterm
diff --git a/developer/src/kmc-ldml/test/fixtures/basic-no-debug.js b/developer/src/kmc-ldml/test/fixtures/basic-no-debug.js
deleted file mode 100644
index 84168414cf8..00000000000
--- a/developer/src/kmc-ldml/test/fixtures/basic-no-debug.js
+++ /dev/null
@@ -1 +0,0 @@
-if(typeof keyman === 'undefined') {console.error('Keyboard requires KeymanWeb 16.0 or later');} else {KeymanWeb.KR(new Keyboard_basic());}function Keyboard_basic() {this.KI="Keyboard_basic";this.KN="TestKbd";this.KMINVER="16.0";this.KV={F: ' 1em "Arial"', K102: 0};this.KV.KLS={TODO_LDML: 2};this.KDU=1;this.KH="";this.KM=0;this.KBVER="1.0.0";this.KMBM=0;this.KVKL={"desktop":{"defaultHint":"none","layer":[{"id":"base","row":[{"id":"0","key":[{"id":"T_hmaqtugha","text":"ħ"},{"id":"T_that","text":"ថា"}]}]}],"displayUnderlying":false}};this.gs=function(t,e){return 0;};}
diff --git a/developer/src/kmc-ldml/test/fixtures/basic.js b/developer/src/kmc-ldml/test/fixtures/basic.js
deleted file mode 100644
index c74798cfac5..00000000000
--- a/developer/src/kmc-ldml/test/fixtures/basic.js
+++ /dev/null
@@ -1,48 +0,0 @@
-if(typeof keyman === 'undefined') {
- console.error('Keyboard requires KeymanWeb 16.0 or later');
-} else {
- KeymanWeb.KR(new Keyboard_basic());
-}
-function Keyboard_basic() {
- this.KI="Keyboard_basic";
- this.KN="TestKbd";
- this.KMINVER="16.0";
- this.KV={F: ' 1em "Arial"', K102: 0};
- this.KV.KLS={
- TODO_LDML: 2
- };
- this.KDU=1;
- this.KH="";
- this.KM=0;
- this.KBVER="1.0.0";
- this.KMBM=0;
- this.KVKL={
- "desktop": {
- "defaultHint": "none",
- "layer": [
- {
- "id": "base",
- "row": [
- {
- "id": "0",
- "key": [
- {
- "id": "T_hmaqtugha",
- "text": "ħ"
- },
- {
- "id": "T_that",
- "text": "ថា"
- }
- ]
- }
- ]
- }
- ],
- "displayUnderlying": false
- }
-};
- this.gs=function(t,e){
- return 0;
- };
-}
diff --git a/developer/src/kmc-ldml/test/fixtures/basic.txt b/developer/src/kmc-ldml/test/fixtures/basic.txt
index b471228a52c..6302a5eb276 100644
--- a/developer/src/kmc-ldml/test/fixtures/basic.txt
+++ b/developer/src/kmc-ldml/test/fixtures/basic.txt
@@ -1,4 +1,6 @@
#
+# Keyman is copyright (C) SIL International. MIT License.
+#
# basic.txt describes the expected output of running kmc against basic.xml. It is used in
# the end-to-end test test-compiler-e2e.ts.
#
@@ -402,7 +404,7 @@ block(layr) # struct COMP_KMXPLUS_LAYR {
01 00 00 00 # count
7B 00 00 00 # KMX_DWORD minDeviceWidth; // 123
# layers 0
- index(strNull,strBase,2) # KMXPLUS_STR id;
+ 00 00 00 00 # KMXPLUS_STR id;
00 00 00 00 # KMX_DWORD mod
00 00 00 00 # KMX_DWORD row index
01 00 00 00 # KMX_DWORD count
@@ -502,7 +504,6 @@ block(strs) # struct COMP_KMXPLUS_STRS {
diff(strs,strSet) sizeof(strSet,2)
diff(strs,strSet2) sizeof(strSet2,2)
diff(strs,strTranTo) sizeof(strTranTo,2)
- diff(strs,strBase) sizeof(strBase,2)
diff(strs,strElemBkspFrom2) sizeof(strElemBkspFrom2,2)
diff(strs,strGapReserved) sizeof(strGapReserved,2)
diff(strs,strHmaqtugha) sizeof(strHmaqtugha,2)
@@ -538,7 +539,6 @@ block(strs) # struct COMP_KMXPLUS_STRS {
#str #0A
block(strSet2) 61 00 62 00 63 00 block(x) 00 00 # 'abc'
block(strTranTo) 61 00 02 03 block(x) 00 00 # 'â' (U+0061 U+0302)
- block(strBase) 62 00 61 00 73 00 65 00 block(x) 00 00 # 'base'
block(strElemBkspFrom2) 65 00 block(x) 00 00 # 'e'
block(strGapReserved) 67 00 61 00 70 00 20 00 28 00 72 00 65 00 73 00 65 00 72 00 76 00 65 00 64 00 29 00 block(x) 00 00 # 'gap (reserved)'
block(strHmaqtugha) 68 00 6d 00 61 00 71 00 74 00 75 00 67 00 68 00 61 00 block(x) 00 00 # 'hmaqtugha'
diff --git a/developer/src/kmc-ldml/test/fixtures/basic.xml b/developer/src/kmc-ldml/test/fixtures/basic.xml
index c8565271ed1..168ee0ef9a0 100644
--- a/developer/src/kmc-ldml/test/fixtures/basic.xml
+++ b/developer/src/kmc-ldml/test/fixtures/basic.xml
@@ -1,4 +1,5 @@
+
+
diff --git a/developer/src/kmc-ldml/test/fixtures/sections/layr/row-keys-whitespace.xml b/developer/src/kmc-ldml/test/fixtures/sections/layr/row-keys-whitespace.xml
new file mode 100644
index 00000000000..f8d1a629d5c
--- /dev/null
+++ b/developer/src/kmc-ldml/test/fixtures/sections/layr/row-keys-whitespace.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/developer/src/kmc-ldml/test/fixtures/sections/vars/fail-badref-7.xml b/developer/src/kmc-ldml/test/fixtures/sections/vars/fail-badref-7.xml
new file mode 100644
index 00000000000..25e7c46a1d4
--- /dev/null
+++ b/developer/src/kmc-ldml/test/fixtures/sections/vars/fail-badref-7.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/developer/src/kmc-ldml/test/fixtures/sections/vars/fail-invalid-identifiers.xml b/developer/src/kmc-ldml/test/fixtures/sections/vars/fail-invalid-identifiers.xml
new file mode 100644
index 00000000000..880864fd4e6
--- /dev/null
+++ b/developer/src/kmc-ldml/test/fixtures/sections/vars/fail-invalid-identifiers.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/developer/src/kmc-ldml/test/helpers/index.ts b/developer/src/kmc-ldml/test/helpers/index.ts
index 0285088acc3..e1463e81169 100644
--- a/developer/src/kmc-ldml/test/helpers/index.ts
+++ b/developer/src/kmc-ldml/test/helpers/index.ts
@@ -1,17 +1,18 @@
-/**
+/*
+ * Keyman is copyright (C) SIL International. MIT License.
+ *
* Helpers and utilities for the Mocha tests.
*/
import 'mocha';
import * as path from 'path';
import { fileURLToPath } from 'url';
import { SectionCompiler, SectionCompilerNew } from '../../src/compiler/section-compiler.js';
-import { util, KMXPlus, UnicodeSetParser, VisualKeyboard } from '@keymanapp/common-types';
+import { util, KMXPlus, UnicodeSetParser } from '@keymanapp/common-types';
import { CompilerEvent, compilerEventFormat, CompilerCallbacks, LDMLKeyboardXMLSourceFileReader, LDMLKeyboardTestDataXMLSourceFile, LDMLKeyboard, } from "@keymanapp/developer-utils";
import { LdmlKeyboardCompiler } from '../../src/main.js'; // make sure main.js compiles
import { assert } from 'chai';
import { KMXPlusMetadataCompiler } from '../../src/compiler/metadata-compiler.js';
import { LdmlCompilerOptions } from '../../src/compiler/ldml-compiler-options.js';
-import { LdmlKeyboardVisualKeyboardCompiler } from '../../src/compiler/visual-keyboard-compiler.js';
import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers';
import KMXPlusFile = KMXPlus.KMXPlusFile;
@@ -21,7 +22,6 @@ import Section = KMXPlus.Section;
import { ElemCompiler, ListCompiler, StrsCompiler } from '../../src/compiler/empty-compiler.js';
import { KmnCompiler } from '@keymanapp/kmc-kmn';
import { VarsCompiler } from '../../src/compiler/vars.js';
-// import Vars = KMXPlus.Vars;
/**
* Builds a path to the fixture with the given path components.
@@ -48,7 +48,7 @@ beforeEach(function() {
afterEach(function() {
if (this.currentTest.state !== 'passed') {
- compilerTestCallbacks.messages.forEach(message => console.log(message.message));
+ compilerTestCallbacks.printMessages();
}
});
@@ -161,28 +161,7 @@ export async function compileKeyboard(inputFilename: string, options: LdmlCompil
return kmx;
}
-export async function compileVisualKeyboard(inputFilename: string, options: LdmlCompilerOptions): Promise {
- const k = new LdmlKeyboardCompiler();
- assert.isTrue(await k.init(compilerTestCallbacks, options));
- const source = k.load(inputFilename);
- checkMessages();
- assert.isNotNull(source, 'k.load should not have returned null');
-
- const valid = await k.validate(source);
- checkMessages();
- assert.isTrue(valid, 'k.validate should not have failed');
-
- const vk = (new LdmlKeyboardVisualKeyboardCompiler(compilerTestCallbacks)).compile(source);
- checkMessages();
- assert.isNotNull(vk, 'LdmlKeyboardVisualKeyboardCompiler.compile should not have returned null');
-
- return vk;
-}
-
export function checkMessages() {
- if(compilerTestCallbacks.messages.length > 0) {
- console.log(compilerTestCallbacks.messages);
- }
assert.isEmpty(compilerTestCallbacks.messages, compilerEventFormat(compilerTestCallbacks.messages));
}
diff --git a/developer/src/kmc-ldml/test/test-keymanweb-compiler.ts b/developer/src/kmc-ldml/test/test-keymanweb-compiler.ts
deleted file mode 100644
index 9a14b38c33d..00000000000
--- a/developer/src/kmc-ldml/test/test-keymanweb-compiler.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import 'mocha';
-import { assert } from 'chai';
-import { checkMessages, compilerTestCallbacks, compilerTestOptions, makePathToFixture } from './helpers/index.js';
-import { LdmlKeyboardKeymanWebCompiler } from '../src/compiler/keymanweb-compiler.js';
-import { LdmlKeyboardCompiler } from '../src/compiler/compiler.js';
-import * as fs from 'fs';
-
-describe('LdmlKeyboardKeymanWebCompiler', function() {
-
- it('should build a .js file', async function() {
- // Let's build basic.xml
- // It should generate content identical to basic.js
- const inputFilename = makePathToFixture('basic.xml');
- const outputFilename = makePathToFixture('basic.js');
- const outputFilenameNoDebug = makePathToFixture('basic-no-debug.js');
-
- // Load input data; we'll use the LDML keyboard compiler loader to save us
- // effort here
- const k = new LdmlKeyboardCompiler();
- await k.init(compilerTestCallbacks, {...compilerTestOptions, saveDebug: true, shouldAddCompilerVersion: false});
- const source = k.load(inputFilename);
- checkMessages();
- assert.isNotNull(source, 'k.load should not have returned null');
-
- // Sanity check ... this is also checked in other tests
- const valid = await k.validate(source);
- checkMessages();
- assert.isTrue(valid, 'k.validate should not have failed');
-
- // Actual test: compile to javascript
- const jsCompiler = new LdmlKeyboardKeymanWebCompiler(compilerTestCallbacks, {...compilerTestOptions, saveDebug: true});
- const output = jsCompiler.compile('basic.xml', source);
- assert.isNotNull(output);
-
- // Does the emitted js match?
- const outputFixture = fs.readFileSync(outputFilename, 'utf-8').replaceAll(/\r\n/g, '\n');
- assert.strictEqual(output, outputFixture);
-
- // Second test: compile to javascript without debug formatting
- const jsCompilerNoDebug = new LdmlKeyboardKeymanWebCompiler(compilerTestCallbacks, {...compilerTestOptions, saveDebug: false});
- const outputNoDebug = jsCompilerNoDebug.compile('basic.xml', source);
- assert.isNotNull(outputNoDebug);
-
- // Does the emitted js match? The nodebug has no newline at end, but allow one in the fixture
- const outputFixtureNoDebug = fs.readFileSync(outputFilenameNoDebug, 'utf-8').replaceAll(/\r\n/g, '\n').trim();
- assert.strictEqual(outputNoDebug, outputFixtureNoDebug);
-
- // TODO(lowpri): consider using Typescript parser to generate AST for further validation
- });
-});
diff --git a/developer/src/kmc-ldml/test/test-layr.ts b/developer/src/kmc-ldml/test/test-layr.ts
index 3cf0701986a..8aeb6041712 100644
--- a/developer/src/kmc-ldml/test/test-layr.ts
+++ b/developer/src/kmc-ldml/test/test-layr.ts
@@ -135,5 +135,30 @@ describe('layr', function () {
LdmlCompilerMessages.Error_InvalidModifier({ layer: '', modifiers: 'caps bogus'}),
]
},
+ {
+ subpath: 'sections/layr/row-keys-whitespace.xml',
+ callback(sect) {
+ const layr = sect;
+ assert.ok(layr);
+ assert.equal(compilerTestCallbacks.messages.length, 0);
+
+ assert.equal(layr.lists?.length, 1);
+ const list0 = layr.lists[0];
+ assert.ok(list0);
+ assert.equal(list0.layers.length, 1);
+ assert.equal(list0.hardware.value, 'us');
+ const layer0 = list0.layers[0];
+ assert.ok(layer0);
+ assert.equal(layer0.rows.length, 2);
+ assert.equal(layer0.id.value, 'base');
+ assert.equal(layer0.mod, constants.keys_mod_none);
+ for(const row of layer0.rows) {
+ assert.ok(row);
+ assert.equal(row.keys.length, 2);
+ assert.equal(row.keys[0]?.value, 'grave');
+ assert.equal(row.keys[1]?.value, 'mistake');
+ }
+ },
+ },
]);
});
diff --git a/developer/src/kmc-ldml/test/test-vars.ts b/developer/src/kmc-ldml/test/test-vars.ts
index 42b5d7df809..8eef17af25c 100644
--- a/developer/src/kmc-ldml/test/test-vars.ts
+++ b/developer/src/kmc-ldml/test/test-vars.ts
@@ -121,6 +121,16 @@ describe('vars', function () {
LdmlCompilerMessages.Error_DuplicateVariable({ids: 'upper, y'})
],
},
+ {
+ subpath: 'sections/vars/fail-invalid-identifiers.xml',
+ errors: [
+ LdmlCompilerMessages.Error_InvalidVariableIdentifer({id: 'invalid-string'}),
+ LdmlCompilerMessages.Error_InvalidVariableIdentifer({id: 'invalid-set'}),
+ LdmlCompilerMessages.Error_InvalidVariableIdentifer({id: 'invalid-uset'}),
+ LdmlCompilerMessages.Error_InvalidVariableIdentifer({id: 'a_marker_name_more_than_32_chars_long'}),
+ LdmlCompilerMessages.Error_InvalidVariableIdentifer({id: '😡'}),
+ ],
+ },
{
subpath: 'sections/vars/fail-uset-props1.xml',
errors: [
@@ -187,7 +197,14 @@ describe('vars', function () {
LdmlCompilerMessages.Error_MissingStringVariable({id: 'missingStringInSet'})
],
},
- ], varsDependencies);
+ {
+ subpath: 'sections/vars/fail-badref-7.xml',
+ errors: [
+ LdmlCompilerMessages.Error_MissingStringVariable({id: 'usedBeforeDefinition'})
+ ],
+ strictErrors: true
+ },
+], varsDependencies);
describe('should match some marker constants', () => {
// neither of these live here, but, common/web/types does not import ldml-keyboard-constants otherwise.
diff --git a/developer/src/kmc-ldml/test/test-visual-keyboard-compiler-e2e.ts b/developer/src/kmc-ldml/test/test-visual-keyboard-compiler-e2e.ts
deleted file mode 100644
index df0aea03918..00000000000
--- a/developer/src/kmc-ldml/test/test-visual-keyboard-compiler-e2e.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import 'mocha';
-import {assert} from 'chai';
-import hextobin from '@keymanapp/hextobin';
-import { KvkFileWriter } from '@keymanapp/common-types';
-import {checkMessages, compilerTestOptions, compileVisualKeyboard, makePathToFixture} from './helpers/index.js';
-
-describe('visual-keyboard-compiler', function() {
- this.slow(500); // 0.5 sec -- json schema validation takes a while
-
- it('should build fixtures', async function() {
- // Let's build basic.xml
- // It should match basic.kvk (built from basic-kvk.txt)
-
- const inputFilename = makePathToFixture('basic.xml');
- const binaryFilename = makePathToFixture('basic-kvk.txt');
-
- // Compile the visual keyboard
- const vk = await compileVisualKeyboard(inputFilename, {...compilerTestOptions, saveDebug: true, shouldAddCompilerVersion: false});
- assert.isNotNull(vk);
-
- // Use the builder to generate the binary output file
- const writer = new KvkFileWriter();
- const code = writer.write(vk);
- checkMessages();
- assert.isNotNull(code);
-
- // Compare output
- let expected = await hextobin(binaryFilename, undefined, {silent:true});
- assert.deepEqual(code, expected);
- });
-});
diff --git a/developer/src/kmc-ldml/test/test-visual-keyboard-compiler.ts b/developer/src/kmc-ldml/test/test-visual-keyboard-compiler.ts
new file mode 100644
index 00000000000..25e3abb47bb
--- /dev/null
+++ b/developer/src/kmc-ldml/test/test-visual-keyboard-compiler.ts
@@ -0,0 +1,236 @@
+/*
+ * Keyman is copyright (C) SIL International. MIT License.
+ */
+import 'mocha';
+import * as path from 'path';
+import {assert} from 'chai';
+import { stripIndent } from 'common-tags';
+
+import { KMX, KvkFileWriter, VisualKeyboard } from '@keymanapp/common-types';
+import hextobin from '@keymanapp/hextobin';
+
+import { checkMessages, compilerTestCallbacks, compilerTestOptions, makePathToFixture } from './helpers/index.js';
+
+import { LdmlKeyboardVisualKeyboardCompiler } from '../src/compiler/visual-keyboard-compiler.js';
+import { LDMLKeyboardXMLSourceFileReader } from '@keymanapp/developer-utils';
+import { LdmlKeyboardCompiler } from '../src/main.js';
+
+describe('visual-keyboard-compiler', function() {
+ this.slow(500); // 0.5 sec -- json schema validation takes a while
+
+ it('should build fixtures', async function() {
+ // Let's build basic.xml
+
+ // It should match basic.kvk (built from basic-kvk.txt)
+ const inputFilename = makePathToFixture('basic.xml');
+ const binaryFilename = makePathToFixture('basic-kvk.txt');
+
+ // Compile the visual keyboard
+ const k = new LdmlKeyboardCompiler();
+ assert.isTrue(await k.init(compilerTestCallbacks, {...compilerTestOptions, saveDebug: true, shouldAddCompilerVersion: false}));
+ const source = k.load(inputFilename);
+ checkMessages();
+ assert.isNotNull(source, 'k.load should not have returned null');
+
+ const valid = await k.validate(source);
+ checkMessages();
+ assert.isTrue(valid, 'k.validate should not have failed');
+
+ let kmx = await k.compile(source);
+ assert(kmx, 'k.compile should not have failed');
+
+ const keyboardId = path.basename(inputFilename, '.xml');
+
+ const vk = (new LdmlKeyboardVisualKeyboardCompiler(compilerTestCallbacks)).compile(kmx.kmxplus, keyboardId);
+ checkMessages();
+ assert.isNotNull(vk, 'LdmlKeyboardVisualKeyboardCompiler.compile should not have returned null');
+ assert.isNotNull(kmx.keyboard.stores.find(store =>
+ store.dwSystemID == KMX.KMXFile.TSS_VISUALKEYBOARD &&
+ store.dpString == keyboardId + '.kvk'
+ ));
+ assert(typeof vk == 'object');
+
+ // Use the builder to generate the binary output file
+ const writer = new KvkFileWriter();
+ const code = writer.write(vk);
+ assert.isEmpty(compilerTestCallbacks.messages);
+ assert.isNotNull(code);
+
+ // Compare output
+ let expected = await hextobin(binaryFilename, undefined, {silent:true});
+
+ assert.deepEqual(code, expected);
+ });
+
+ it('should support various modifiers', async function() {
+ const xml = stripIndent`
+
+
+
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+
+
+ `;
+
+ const vk = await loadVisualKeyboardFromXml(xml, 'test');
+
+ assert.equal(vk.keys.length, 5);
+ assert.equal(vk.keys[0].shift, VisualKeyboard.VisualKeyboardShiftState.KVKS_NORMAL);
+ assert.equal(vk.keys[1].shift, VisualKeyboard.VisualKeyboardShiftState.KVKS_SHIFT);
+ assert.equal(vk.keys[2].shift, VisualKeyboard.VisualKeyboardShiftState.KVKS_RALT);
+ assert.equal(vk.keys[3].shift, VisualKeyboard.VisualKeyboardShiftState.KVKS_RCTRL | VisualKeyboard.VisualKeyboardShiftState.KVKS_RALT);
+ assert.equal(vk.keys[4].shift, VisualKeyboard.VisualKeyboardShiftState.KVKS_SHIFT | VisualKeyboard.VisualKeyboardShiftState.KVKS_RALT);
+ });
+
+ it('should emit the correct associated keyboard id', async function() {
+ const xml = stripIndent`
+
+
+
+
+
+
+
+ |
+
+
+ `;
+
+ const vk = await loadVisualKeyboardFromXml(xml, 'test');
+
+ assert.equal(vk.header.associatedKeyboard, 'test');
+ });
+
+ it('should support ', async function() {
+ const xml = stripIndent`
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+ `;
+
+ const vk = await loadVisualKeyboardFromXml(xml, 'test');
+
+ assert.equal(vk.keys.length, 2);
+ assert.equal(vk.keys[0].text, 'C');
+ assert.equal(vk.keys[1].text, 'D');
+ });
+
+ it('should correctly decode \\u{xxxx}', async function() {
+ const xml = stripIndent`
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+ `;
+
+ const vk = await loadVisualKeyboardFromXml(xml, 'test');
+
+ assert.equal(vk.keys.length, 2);
+ assert.equal(vk.keys[0].text, '\u{0e80}');
+ assert.equal(vk.keys[1].text, '\u{0e81}');
+ });
+
+ it('should read string variables in key.output', async function() {
+ const xml = stripIndent`
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+ `;
+
+ const vk = await loadVisualKeyboardFromXml(xml, 'test');
+
+ assert.equal(vk.keys.length, 1);
+ assert.equal(vk.keys[0].text, '2');
+ });
+
+ it('should read string variables in display.display', async function() {
+ const xml = stripIndent`
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+ `;
+
+ const vk = await loadVisualKeyboardFromXml(xml, 'test');
+
+ assert.equal(vk.keys.length, 1);
+ assert.equal(vk.keys[0].text, '2');
+ });
+});
+
+async function loadVisualKeyboardFromXml(xml: string, id: string) {
+ const data = new TextEncoder().encode(xml);
+ assert.isOk(data);
+
+ const reader = new LDMLKeyboardXMLSourceFileReader(compilerTestOptions.readerOptions, compilerTestCallbacks);
+ const source = reader.load(data);
+ assert.isEmpty(compilerTestCallbacks.messages);
+ assert.isOk(source);
+
+ const k = new LdmlKeyboardCompiler();
+ assert.isTrue(await k.init(compilerTestCallbacks, compilerTestOptions));
+
+ const kmx = await k.compile(source);
+ assert(kmx, 'k.compile should not have failed');
+
+ const vk = (new LdmlKeyboardVisualKeyboardCompiler(compilerTestCallbacks)).compile(kmx.kmxplus, id);
+ assert(typeof vk == 'object');
+ assert.isEmpty(compilerTestCallbacks.messages);
+ assert.isOk(vk);
+
+ return vk;
+}
diff --git a/developer/src/kmc-model-info/.eslintrc.cjs b/developer/src/kmc-model-info/.eslintrc.cjs
index d37a4610738..494ae6b00ab 100644
--- a/developer/src/kmc-model-info/.eslintrc.cjs
+++ b/developer/src/kmc-model-info/.eslintrc.cjs
@@ -8,7 +8,7 @@ module.exports = {
overrides: [
{
files: "src/**/*.ts",
- extends: ["../../../common/web/eslint/eslintNoNodeImports.js"],
+ extends: ["../../../common/tools/eslint/eslintNoNodeImports.js"],
}
],
rules: {
diff --git a/developer/src/kmc-model-info/build.sh b/developer/src/kmc-model-info/build.sh
index 334bfbd07bd..b1ba95c34b8 100755
--- a/developer/src/kmc-model-info/build.sh
+++ b/developer/src/kmc-model-info/build.sh
@@ -9,7 +9,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
builder_describe "Build Keyman kmc Lexical Model model-info Compiler module" \
"@/common/web/types" \
- "@/common/models/types" \
"clean" \
"configure" \
"build" \
diff --git a/developer/src/kmc-model-info/package.json b/developer/src/kmc-model-info/package.json
index 23297a07ba2..20be72d1899 100644
--- a/developer/src/kmc-model-info/package.json
+++ b/developer/src/kmc-model-info/package.json
@@ -31,7 +31,7 @@
},
"dependencies": {
"@keymanapp/common-types": "*",
- "@keymanapp/models-types": "*",
+ "@keymanapp/common-types": "*",
"@keymanapp/developer-utils": "*"
},
"devDependencies": {
diff --git a/developer/src/kmc-model-info/tsconfig.json b/developer/src/kmc-model-info/tsconfig.json
index 24475de0198..e4d68f11bfd 100644
--- a/developer/src/kmc-model-info/tsconfig.json
+++ b/developer/src/kmc-model-info/tsconfig.json
@@ -10,7 +10,6 @@
],
"references": [
{ "path": "../../../common/web/types" },
- { "path": "../../../common/models/types" },
{ "path": "../common/web/utils" },
]
}
diff --git a/developer/src/kmc-model/.eslintrc.cjs b/developer/src/kmc-model/.eslintrc.cjs
index 3d339676e2e..efa3d1a96a4 100644
--- a/developer/src/kmc-model/.eslintrc.cjs
+++ b/developer/src/kmc-model/.eslintrc.cjs
@@ -9,7 +9,7 @@ module.exports = {
overrides: [
{
files: "src/**/*.ts",
- extends: ["../../../common/web/eslint/eslintNoNodeImports.js"],
+ extends: ["../../../common/tools/eslint/eslintNoNodeImports.js"],
},
],
rules: {
diff --git a/developer/src/kmc-model/build.sh b/developer/src/kmc-model/build.sh
index c549648b451..372a145312a 100755
--- a/developer/src/kmc-model/build.sh
+++ b/developer/src/kmc-model/build.sh
@@ -8,12 +8,10 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
-# TODO: "@/common/models/types" \
-
builder_describe "Keyman kmc Lexical Model Compiler module" \
"@/common/web/keyman-version" \
"@/developer/src/common/web/test-helpers" \
- "@/common/models/templates test" \
+ "@/web/src/engine/predictive-text/templates/ test" \
"clean" \
"configure" \
"build" \
diff --git a/developer/src/kmc-model/package.json b/developer/src/kmc-model/package.json
index da73aabe7b1..9f95de51f8d 100644
--- a/developer/src/kmc-model/package.json
+++ b/developer/src/kmc-model/package.json
@@ -32,7 +32,7 @@
"dependencies": {
"@keymanapp/common-types": "*",
"@keymanapp/keyman-version": "*",
- "@keymanapp/models-types": "*",
+ "@keymanapp/common-types": "*",
"typescript": "^5.4.5"
},
"devDependencies": {
diff --git a/developer/src/kmc-model/src/join-word-breaker-decorator.ts b/developer/src/kmc-model/src/join-word-breaker-decorator.ts
index 6d0426938a9..dec58d583ab 100644
--- a/developer/src/kmc-model/src/join-word-breaker-decorator.ts
+++ b/developer/src/kmc-model/src/join-word-breaker-decorator.ts
@@ -1,4 +1,4 @@
-///
+import { Span, WordBreakingFunction } from '@keymanapp/common-types';
/**
* Returns a word breaker that joins spans of an existing word breaker.
diff --git a/developer/src/kmc-model/src/lexical-model.ts b/developer/src/kmc-model/src/lexical-model.ts
index 32d5bf5775f..f0fd95c19e6 100644
--- a/developer/src/kmc-model/src/lexical-model.ts
+++ b/developer/src/kmc-model/src/lexical-model.ts
@@ -3,6 +3,8 @@
* the LMLayer's internal worker code, so we provide those definitions too.
*/
+import { CasingFunction, LexicalModelPunctuation, WordBreakingFunction } from '@keymanapp/common-types';
+
export interface LexicalModelDeclaration {
readonly format: 'trie-1.0'|'fst-foma-1.0'|'custom-1.0',
//... metadata ...
diff --git a/developer/src/kmc-model/src/model-defaults.ts b/developer/src/kmc-model/src/model-defaults.ts
index 541cadcb497..4d6c40d3a11 100644
--- a/developer/src/kmc-model/src/model-defaults.ts
+++ b/developer/src/kmc-model/src/model-defaults.ts
@@ -1,3 +1,5 @@
+import { CasingForm, CasingFunction } from '@keymanapp/common-types';
+
/**
* Converts wordforms into an indexable form. It does this by
* normalizing the letter case of characters INDIVIDUALLY (to disregard
@@ -104,4 +106,4 @@ export function defaultApplyCasing(casing: CasingForm, text: string): string {
return text.substring(0, headUnitLength).toUpperCase() // head - uppercased
.concat(text.substring(headUnitLength)); // tail - lowercased
}
-}
\ No newline at end of file
+}
diff --git a/developer/src/kmc-model/src/model-definitions.ts b/developer/src/kmc-model/src/model-definitions.ts
index d05a8254ce5..ccef469c91e 100644
--- a/developer/src/kmc-model/src/model-definitions.ts
+++ b/developer/src/kmc-model/src/model-definitions.ts
@@ -5,6 +5,7 @@ import { defaultApplyCasing,
import KEYMAN_VERSION from "@keymanapp/keyman-version";
import { LexicalModelSource, WordformToKeySpec } from "./lexical-model.js";
+import { CasingForm, CasingFunction } from '@keymanapp/common-types';
/**
* Processes certain defined model behaviors in such a way that the needed closures
@@ -219,4 +220,4 @@ export class ModelDefinitions {
}
// Because it references the class field, this line must come afterward.
-const PSEUDOCLOSURE = ModelDefinitions.COMPILED_NAME;
\ No newline at end of file
+const PSEUDOCLOSURE = ModelDefinitions.COMPILED_NAME;
diff --git a/developer/src/kmc-model/src/script-overrides-decorator.ts b/developer/src/kmc-model/src/script-overrides-decorator.ts
index 544f1b03869..ebf08b819e2 100644
--- a/developer/src/kmc-model/src/script-overrides-decorator.ts
+++ b/developer/src/kmc-model/src/script-overrides-decorator.ts
@@ -1,3 +1,4 @@
+import { Span, WordBreakingFunction } from '@keymanapp/common-types';
import { OverrideScriptDefaults } from "./lexical-model.js";
import { ModelCompilerError, ModelCompilerMessages } from "./model-compiler-messages.js";
diff --git a/developer/src/kmc-model/test/helpers/index.ts b/developer/src/kmc-model/test/helpers/index.ts
index e7d274f5712..d58e0834d11 100644
--- a/developer/src/kmc-model/test/helpers/index.ts
+++ b/developer/src/kmc-model/test/helpers/index.ts
@@ -1,5 +1,3 @@
-///
-
/**
* Helpers and utilities for the Mocha tests.
*/
diff --git a/developer/src/kmc-model/test/test-compile-model-with-pseudoclosure.ts b/developer/src/kmc-model/test/test-compile-model-with-pseudoclosure.ts
index 08247e17c1d..049776ec201 100644
--- a/developer/src/kmc-model/test/test-compile-model-with-pseudoclosure.ts
+++ b/developer/src/kmc-model/test/test-compile-model-with-pseudoclosure.ts
@@ -4,6 +4,7 @@ import 'mocha';
import { makePathToFixture, compileModelSourceCode } from './helpers/index.js';
import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers';
+import { CasingForm, CasingFunction } from '@keymanapp/common-types';
describe('LexicalModelCompiler - pseudoclosure compilation + use', function () {
const callbacks = new TestCompilerCallbacks();
diff --git a/developer/src/kmc-model/test/test-compile-trie.ts b/developer/src/kmc-model/test/test-compile-trie.ts
index a9829568370..845022cef5c 100644
--- a/developer/src/kmc-model/test/test-compile-trie.ts
+++ b/developer/src/kmc-model/test/test-compile-trie.ts
@@ -8,6 +8,7 @@ import { createTrieDataStructure } from '../src/build-trie.js';
import { ModelCompilerError } from '../src/model-compiler-messages.js';
import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers';
import { TrieModel } from '@keymanapp/models-templates';
+import { Span } from '@keymanapp/common-types';
describe('LexicalModelCompiler', function () {
const callbacks = new TestCompilerCallbacks();
diff --git a/developer/src/kmc-model/test/test-default-search-term-to-key.ts b/developer/src/kmc-model/test/test-default-search-term-to-key.ts
index b777c7c79c3..0f8120e6bae 100644
--- a/developer/src/kmc-model/test/test-default-search-term-to-key.ts
+++ b/developer/src/kmc-model/test/test-default-search-term-to-key.ts
@@ -4,6 +4,7 @@ import {assert} from 'chai';
import { defaultSearchTermToKey,
defaultCasedSearchTermToKey,
defaultApplyCasing } from '../src/model-defaults.js';
+import { CasingForm, CasingFunction } from '@keymanapp/common-types';
describe('The default searchTermToKey() function', function () {
diff --git a/developer/src/kmc-model/test/test-join-word-breaker.ts b/developer/src/kmc-model/test/test-join-word-breaker.ts
index b08411cb561..ff220088f5c 100644
--- a/developer/src/kmc-model/test/test-join-word-breaker.ts
+++ b/developer/src/kmc-model/test/test-join-word-breaker.ts
@@ -1,6 +1,7 @@
import { assert } from "chai";
import defaultWordBreaker from '@keymanapp/models-wordbreakers';
import {decorateWithJoin} from '../src/join-word-breaker-decorator.js';
+import { Span } from '@keymanapp/common-types';
describe('The join word breaker decorator', function () {
it('should decorate an existing word breaker', function () {
diff --git a/developer/src/kmc-model/test/test-model-definitions.ts b/developer/src/kmc-model/test/test-model-definitions.ts
index 297bb1a995b..ee569a01b1a 100644
--- a/developer/src/kmc-model/test/test-model-definitions.ts
+++ b/developer/src/kmc-model/test/test-model-definitions.ts
@@ -2,6 +2,7 @@ import 'mocha';
import { assert } from 'chai';
import { ModelDefinitions } from '../src/model-definitions.js';
import { LexicalModelSource } from '../src/lexical-model.js';
+import { CasingForm, CasingFunction } from '@keymanapp/common-types';
describe('Model definition pseudoclosures', function () {
describe('14.0 defaults', function() {
@@ -195,4 +196,4 @@ describe('Model definition pseudoclosures', function () {
});
}
});
-});
\ No newline at end of file
+});
diff --git a/developer/src/kmc-model/test/test-override-script-defaults.ts b/developer/src/kmc-model/test/test-override-script-defaults.ts
index a0500320fd9..b8afb18900b 100644
--- a/developer/src/kmc-model/test/test-override-script-defaults.ts
+++ b/developer/src/kmc-model/test/test-override-script-defaults.ts
@@ -1,6 +1,7 @@
import { assert } from "chai";
import defaultWordBreaker from '@keymanapp/models-wordbreakers';
import {decorateWithScriptOverrides} from '../src/script-overrides-decorator.js';
+import { Span } from '@keymanapp/common-types';
const THIN_SPACE = "\u2009";
diff --git a/developer/src/kmc-model/tsconfig.json b/developer/src/kmc-model/tsconfig.json
index 724ebd74d0b..2400856d265 100644
--- a/developer/src/kmc-model/tsconfig.json
+++ b/developer/src/kmc-model/tsconfig.json
@@ -12,7 +12,6 @@
],
"references": [
{ "path": "../../../common/web/keyman-version" },
- { "path": "../../../common/models/types" },
{ "path": "../../../common/web/types" },
]
}
diff --git a/developer/src/kmc-package/.eslintrc.cjs b/developer/src/kmc-package/.eslintrc.cjs
index 09038ae9291..41a487fe41d 100644
--- a/developer/src/kmc-package/.eslintrc.cjs
+++ b/developer/src/kmc-package/.eslintrc.cjs
@@ -6,7 +6,7 @@ module.exports = {
overrides: [
{
files:"src/**/*.ts",
- extends: ["../../../common/web/eslint/eslintNoNodeImports.js"],
+ extends: ["../../../common/tools/eslint/eslintNoNodeImports.js"],
}
],
rules: {
diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts
index 04688d0e5a2..1708d69704c 100644
--- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts
+++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts
@@ -1,4 +1,4 @@
-import { xml2js } from '@keymanapp/developer-utils';
+import { KeymanXMLReader } from '@keymanapp/developer-utils';
import JSZip from 'jszip';
import KEYMAN_VERSION from "@keymanapp/keyman-version";
@@ -180,12 +180,10 @@ export class KmpCompiler implements KeymanCompiler {
const kpsPackage = (() => {
let a: KpsFile.KpsPackage;
- let parser = new xml2js.Parser({
- explicitArray: false
- });
try {
- parser.parseString(data, (e: unknown, r: unknown) => { if(e) throw e; a = r as KpsFile.KpsPackage });
+ a = new KeymanXMLReader('kps')
+ .parse(data.toString()) as KpsFile.KpsPackage;
} catch(e) {
this.callbacks.reportMessage(PackageCompilerMessages.Error_InvalidPackageFile({e}));
}
diff --git a/developer/src/kmc/build.sh b/developer/src/kmc/build.sh
index a3bf11774a9..a39808a546f 100755
--- a/developer/src/kmc/build.sh
+++ b/developer/src/kmc/build.sh
@@ -16,6 +16,7 @@ builder_describe "Build Keyman Keyboard Compiler kmc" \
"@/common/include" \
"@/common/web/keyman-version" \
"@/common/web/types" \
+ "@/core/include/ldml" \
"@/developer/src/common/web/utils" \
"@/developer/src/kmc-analyze" \
"@/developer/src/kmc-keyboard-info" \
diff --git a/developer/src/kmc/package.json b/developer/src/kmc/package.json
index d6486e16852..4d36bb1e98c 100644
--- a/developer/src/kmc/package.json
+++ b/developer/src/kmc/package.json
@@ -44,7 +44,7 @@
"@keymanapp/kmc-model": "*",
"@keymanapp/kmc-model-info": "*",
"@keymanapp/kmc-package": "*",
- "@keymanapp/models-types": "*",
+ "@keymanapp/common-types": "*",
"@sentry/node": "^7.57.0",
"chalk": "^2.4.2",
"commander": "^10.0.0",
diff --git a/developer/src/kmcmplib/include/kmcmplibapi.h b/developer/src/kmcmplib/include/kmcmplibapi.h
index c41ba53eaa0..536d4fff0d4 100644
--- a/developer/src/kmcmplib/include/kmcmplibapi.h
+++ b/developer/src/kmcmplib/include/kmcmplibapi.h
@@ -1,5 +1,7 @@
#pragma once
+#include
+
#include
#include
diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp
index 313cc859e59..57702ee1f0f 100644
--- a/developer/src/kmcmplib/src/Compiler.cpp
+++ b/developer/src/kmcmplib/src/Compiler.cpp
@@ -2213,10 +2213,9 @@ KMX_DWORD GetXStringImpl(PKMX_WCHAR tstr, PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX
{
KMX_WCHAR *context = NULL;
- KMX_WCHAR sep_com[3] = u" ,";
- PKMX_WCHAR p_sep_com = sep_com;
- r = u16tok(q, p_sep_com, &context); // I3481
+ r = u16tok(q, ',', &context);
if (!r) return KmnCompilerMessages::ERROR_InvalidIndex;
+ r = u16trim(r);
for (i = 0; i < fk->cxStoreArray; i++)
{
@@ -2226,8 +2225,10 @@ KMX_DWORD GetXStringImpl(PKMX_WCHAR tstr, PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX
kmcmp::CheckStoreUsage(fk, i, TRUE, FALSE, FALSE);
- r = u16tok(NULL, p_sep_com, &context); // I3481
- if (!r || !*r || !isIntegerWstring(r) || atoiW(r) < 1) return KmnCompilerMessages::ERROR_InvalidIndex;
+ r = context;
+ if (!r || !*r ) return KmnCompilerMessages::ERROR_InvalidIndex;
+ r = u16trim(r);
+ if (!isIntegerWstring(r) || atoiW(r) < 1) return KmnCompilerMessages::ERROR_InvalidIndex;
}
tstr[mx++] = UC_SENTINEL;
tstr[mx++] = CODE_INDEX;
diff --git a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp
index aab01f8131c..39c47372d42 100644
--- a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp
+++ b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp
@@ -26,7 +26,9 @@ KMX_DWORD ProcessKeyLineImpl(PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX_BOOL IsUnico
namespace kmcmp {
extern int nErrors;
+ extern int currentLine;
extern int ErrChr;
+ extern std::string messageFilename;
extern int BeginLine[4];
extern int CompileTarget;
}
@@ -49,7 +51,9 @@ class CompilerTest : public testing::Test {
kmcmp::msgproc = msgproc_collect;
msgproc_errors.clear();
kmcmp::nErrors = 0;
+ kmcmp::currentLine = 0;
kmcmp::ErrChr = 0;
+ kmcmp::messageFilename = "";
kmcmp::BeginLine[BEGIN_ANSI] = -1;
kmcmp::BeginLine[BEGIN_UNICODE] = -1;
kmcmp::BeginLine[BEGIN_NEWCONTEXT] = -1;
@@ -126,42 +130,48 @@ TEST_F(CompilerTest, wstrtostr_test) {
EXPECT_EQ(0, strcmp("", wstrtostr((PKMX_WCHAR)u"")));
};
-// TEST_F(CompilerTest, ReportCompilerMessage_test) {
-// msgproc = msgproc_true_stub;
-// kmcmp::ErrChr = 0;
-
-// // SevFatal
-// EXPECT_EQ(0, kmcmp::nErrors);
-// EXPECT_EQ(SevFatal, KmnCompilerMessages::FATAL_CannotCreateTempfile & SevFatal);
-// EXPECT_TRUE(ReportCompilerMessage(KmnCompilerMessages::FATAL_CannotCreateTempfile));
-// EXPECT_EQ(1, kmcmp::nErrors);
-
-// // SevError
-// EXPECT_EQ(SevError, KmnCompilerMessages::ERROR_InvalidLayoutLine & SevError);
-// EXPECT_FALSE(ReportCompilerMessage(KmnCompilerMessages::ERROR_InvalidLayoutLine));
-// EXPECT_EQ(2, kmcmp::nErrors);
-
-// // Unknown
-// const KMX_DWORD UNKNOWN_ERROR = 0x00004FFF; // top of range ERROR
-// EXPECT_EQ(SevError, UNKNOWN_ERROR & SevError);
-// EXPECT_FALSE(ReportCompilerMessage(UNKNOWN_ERROR));
-// sprintf(expected, "Unknown error %x", UNKNOWN_ERROR);
-// EXPECT_EQ(3, kmcmp::nErrors);
-
-// // ErrChr
-// const int ERROR_CHAR_INDEX = 42;
-// kmcmp::ErrChr = ERROR_CHAR_INDEX ;
-// EXPECT_EQ(SevError, KmnCompilerMessages::ERROR_InvalidLayoutLine & SevError);
-// EXPECT_FALSE(ReportCompilerMessage(KmnCompilerMessages::ERROR_InvalidLayoutLine));
-// kmcmp::ErrChr = 0;
-// EXPECT_EQ(4, kmcmp::nErrors);
-
-// // msgproc returns FALSE
-// msgproc = msgproc_false_stub;
-// EXPECT_EQ(SevError, KmnCompilerMessages::ERROR_InvalidLayoutLine & SevError);
-// EXPECT_TRUE(ReportCompilerMessage(KmnCompilerMessages::ERROR_InvalidLayoutLine));
-// EXPECT_EQ(6, kmcmp::nErrors);
-// };
+TEST_F(CompilerTest, ReportCompilerMessage_test) {
+ kmcmp::msgproc = msgproc_collect;
+ kmcmp::currentLine = 42;
+ std::vector params{"parameter"};
+ kmcmp::messageFilename = "filename";
+ kmcmp::ErrChr = 0;
+
+ // SevFatal
+ EXPECT_EQ(0, kmcmp::nErrors);
+ EXPECT_EQ(SevFatal, KmnCompilerMessages::FATAL_CannotCreateTempfile & SevFatal);
+ ReportCompilerMessage(KmnCompilerMessages::FATAL_CannotCreateTempfile, params);
+ EXPECT_EQ(1, kmcmp::nErrors);
+ EXPECT_EQ(KmnCompilerMessages::FATAL_CannotCreateTempfile, msgproc_errors[0].errorCode);
+ EXPECT_EQ(kmcmp::currentLine+1, msgproc_errors[0].lineNumber);
+ EXPECT_EQ(kmcmp::ErrChr, msgproc_errors[0].columnNumber);
+ EXPECT_TRUE(msgproc_errors[0].filename == kmcmp::messageFilename);
+ EXPECT_TRUE(msgproc_errors[0].parameters == params);
+
+ // SevError
+ EXPECT_EQ(SevError, KmnCompilerMessages::ERROR_InvalidLayoutLine & SevError);
+ ReportCompilerMessage(KmnCompilerMessages::ERROR_InvalidLayoutLine);
+ EXPECT_EQ(2, kmcmp::nErrors);
+ EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidLayoutLine, msgproc_errors[1].errorCode);
+
+ // SevWarn
+ EXPECT_EQ(SevWarn, KmnCompilerMessages::WARN_ReservedCharacter & SevWarn);
+ ReportCompilerMessage(KmnCompilerMessages::WARN_ReservedCharacter);
+ EXPECT_EQ(2, kmcmp::nErrors);
+ EXPECT_EQ(KmnCompilerMessages::WARN_ReservedCharacter, msgproc_errors[2].errorCode);
+
+ // SevHint
+ EXPECT_EQ(SevHint, KmnCompilerMessages::HINT_NonUnicodeFile & SevHint);
+ ReportCompilerMessage(KmnCompilerMessages::HINT_NonUnicodeFile);
+ EXPECT_EQ(2, kmcmp::nErrors);
+ EXPECT_EQ(KmnCompilerMessages::HINT_NonUnicodeFile, msgproc_errors[3].errorCode);
+
+ // SevInfo
+ EXPECT_EQ(SevInfo, KmnCompilerMessages::INFO_MinimumCoreEngineVersion & SevInfo);
+ ReportCompilerMessage(KmnCompilerMessages::INFO_MinimumCoreEngineVersion);
+ EXPECT_EQ(2, kmcmp::nErrors);
+ EXPECT_EQ(KmnCompilerMessages::INFO_MinimumCoreEngineVersion, msgproc_errors[4].errorCode);
+};
TEST_F(CompilerTest, ProcessBeginLine_test) {
KMX_WCHAR str[LINESIZE];
@@ -795,7 +805,7 @@ TEST_F(CompilerTest, GetXStringImpl_type_a_test) {
u16cpy(str, u"any()");
EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidAny, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- // KmnCompilerMessages::ERROR_InvalidAny, space in delimiters (see I11814, I11937, #11910, #11894, #11938)
+ // KmnCompilerMessages::ERROR_InvalidAny, space in delimiters (see #11814, #11937, #11910, #11894, #11938)
u16cpy(str, u"any( )");
EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidAny, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
@@ -827,15 +837,13 @@ TEST_F(CompilerTest, GetXStringImpl_type_a_test) {
u16cpy(str, u"any( b)");
file_store[1].dpString = (PKMX_WCHAR)u"abc"; // non-empty
EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- const KMX_WCHAR tstr_any_space_before_valid[] = { UC_SENTINEL, CODE_ANY, 2, 0 };
- EXPECT_EQ(0, u16cmp(tstr_any_space_before_valid, tstr));
+ EXPECT_EQ(0, u16cmp(tstr_any_valid, tstr));
- // space after store, valid (see I11937, #11938)
+ // space after store, valid (see #11937, #11938)
u16cpy(str, u"any(b )");
file_store[1].dpString = (PKMX_WCHAR)u"abc"; // non-empty
EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- const KMX_WCHAR tstr_any_space_after_valid[] = { UC_SENTINEL, CODE_ANY, 2, 0 };
- EXPECT_EQ(0, u16cmp(tstr_any_space_after_valid, tstr));
+ EXPECT_EQ(0, u16cmp(tstr_any_valid, tstr));
}
// tests strings starting with 'b'
@@ -877,7 +885,7 @@ TEST_F(CompilerTest, GetXStringImpl_type_b_test) {
u16cpy(str, u"baselayout()");
EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidToken, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- // baselayout, space in delimiters (see I11814, I11937, #11910, #11894, #11938)
+ // baselayout, space in delimiters (see #11814, #11937, #11910, #11894, #11938)
fileKeyboard.version = VERSION_90;
u16cpy(str, u"baselayout( )");
EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidToken, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
@@ -902,17 +910,15 @@ TEST_F(CompilerTest, GetXStringImpl_type_b_test) {
fileKeyboard.dpStoreArray = nullptr;
u16cpy(str, u"baselayout( beep)");
EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- const KMX_WCHAR tstr_baselayout_space_before_valid[] = { UC_SENTINEL, CODE_IFSYSTEMSTORE, TSS_BASELAYOUT+1, 2, 1, 0 };
- EXPECT_EQ(0, u16cmp(tstr_baselayout_space_before_valid, tstr));
+ EXPECT_EQ(0, u16cmp(tstr_baselayout_valid, tstr));
- // baselayout, space after argument, valid (see I11937, #11938)
+ // baselayout, space after argument, valid (see #11937, #11938)
fileKeyboard.version = VERSION_90;
fileKeyboard.cxStoreArray = 0;
fileKeyboard.dpStoreArray = nullptr;
u16cpy(str, u"baselayout(beep )");
EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- const KMX_WCHAR tstr_baselayout_space_after_valid[] = { UC_SENTINEL, CODE_IFSYSTEMSTORE, TSS_BASELAYOUT+1, 2, 1, 0 };
- EXPECT_EQ(0, u16cmp(tstr_baselayout_space_after_valid, tstr));
+ EXPECT_EQ(0, u16cmp(tstr_baselayout_valid, tstr));
}
// tests strings starting with 'i'
@@ -953,7 +959,7 @@ TEST_F(CompilerTest, GetXStringImpl_type_i_test) {
u16cpy(str, u"if()");
EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidIf, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- // if, space in delimiters (see I11814, I11937, #11910, #11894, #11938)
+ // if, space in delimiters (see #11814, #11937, #11910, #11894, #11938)
fileKeyboard.version = VERSION_80;
fileKeyboard.dwFlags = 0u;
u16cpy(str, u"if( )");
@@ -1012,8 +1018,7 @@ TEST_F(CompilerTest, GetXStringImpl_type_i_test) {
option[1].fIsOption = TRUE;
u16cpy(str, u"if(b =beep)");
EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- const KMX_WCHAR tstr_if_option_space_before_assign_valid[] = { UC_SENTINEL, CODE_IFOPT, 2, 2, 4, 0 };
- EXPECT_EQ(0, u16cmp(tstr_if_option_space_before_assign_valid, tstr));
+ EXPECT_EQ(0, u16cmp(tstr_if_option_valid, tstr));
// if, option, equal, space before rhs, valid
fileKeyboard.version = VERSION_80;
@@ -1022,18 +1027,16 @@ TEST_F(CompilerTest, GetXStringImpl_type_i_test) {
option[1].fIsOption = TRUE;
u16cpy(str, u"if(b= beep)");
EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- const KMX_WCHAR tstr_if_option_space_before_rhs_valid[] = { UC_SENTINEL, CODE_IFOPT, 2, 2, 4, 0 };
- EXPECT_EQ(0, u16cmp(tstr_if_option_space_before_rhs_valid, tstr));
+ EXPECT_EQ(0, u16cmp(tstr_if_option_valid, tstr));
- // if, option, equal, space after rhs, valid (see I11937, #11938)
+ // if, option, equal, space after rhs, valid (see #11937, #11938)
fileKeyboard.version = VERSION_80;
fileKeyboard.cxStoreArray = 3u;
fileKeyboard.dpStoreArray = option;
option[1].fIsOption = TRUE;
u16cpy(str, u"if(b=beep )");
EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- const KMX_WCHAR tstr_if_option_space_after_rhs_valid[] = { UC_SENTINEL, CODE_IFOPT, 2, 2, 4, 0 };
- EXPECT_EQ(0, u16cmp(tstr_if_option_space_after_rhs_valid, tstr));
+ EXPECT_EQ(0, u16cmp(tstr_if_option_valid, tstr));
delete[] option;
PFILE_STORE file_store = new FILE_STORE[100];
@@ -1054,7 +1057,7 @@ TEST_F(CompilerTest, GetXStringImpl_type_i_test) {
u16cpy(str, u"index()");
EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidIndex, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- // index, space in delimiters (see I11814, I11937, #11910, #11894, #11938)
+ // index, space in delimiters (see #11814, #11937, #11910, #11894, #11938)
u16cpy(str, u"index( )");
EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidIndex, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
@@ -1091,34 +1094,50 @@ TEST_F(CompilerTest, GetXStringImpl_type_i_test) {
fileKeyboard.dpStoreArray = file_store;
u16cpy(str, u"index(b,4)");
EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- const KMX_WCHAR tstr_index_comma_valid[] = { UC_SENTINEL, CODE_INDEX, 2, 4, 0 };
- EXPECT_EQ(0, u16cmp(tstr_index_comma_valid, tstr));
+ const KMX_WCHAR tstr_index_valid[] = { UC_SENTINEL, CODE_INDEX, 2, 4, 0 };
+ EXPECT_EQ(0, u16cmp(tstr_index_valid, tstr));
// index, space before store, comma, valid
fileKeyboard.cxStoreArray = 3u;
fileKeyboard.dpStoreArray = file_store;
u16cpy(str, u"index( b,4)");
EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- const KMX_WCHAR tstr_index_initial_space_and_comma_valid[] = { UC_SENTINEL, CODE_INDEX, 2, 4, 0 };
- EXPECT_EQ(0, u16cmp(tstr_index_initial_space_and_comma_valid, tstr));
+ EXPECT_EQ(0, u16cmp(tstr_index_valid, tstr));
+
+ // index, space after store, comma, valid
+ fileKeyboard.cxStoreArray = 3u;
+ fileKeyboard.dpStoreArray = file_store;
+ u16cpy(str, u"index(b ,4)");
+ EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+ EXPECT_EQ(0, u16cmp(tstr_index_valid, tstr));
// index, comma and space, valid
fileKeyboard.cxStoreArray = 3u;
fileKeyboard.dpStoreArray = file_store;
u16cpy(str, u"index(b, 4)");
EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- const KMX_WCHAR tstr_index_comma_and_space_valid[] = { UC_SENTINEL, CODE_INDEX, 2, 4, 0 };
- EXPECT_EQ(0, u16cmp(tstr_index_comma_and_space_valid, tstr));
+ EXPECT_EQ(0, u16cmp(tstr_index_valid, tstr));
- // index, space, valid ... should not be valid (see issue #11833)
- u16cpy(str, u"index(b 4)");
+ // index, comma, space after offset, valid
fileKeyboard.cxStoreArray = 3u;
fileKeyboard.dpStoreArray = file_store;
+ u16cpy(str, u"index(b,4 )");
EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- const KMX_WCHAR tstr_index_space_valid[] = { UC_SENTINEL, CODE_INDEX, 2, 4, 0 };
- EXPECT_EQ(0, u16cmp(tstr_index_space_valid, tstr));
+ EXPECT_EQ(0, u16cmp(tstr_index_valid, tstr));
+
+ // index, space, KmnCompilerMessages::ERROR_StoreDoesNotExist (see issue #11833)
+ u16cpy(str, u"index(b 4)"); // store name appears to be 'b 4'
+ fileKeyboard.cxStoreArray = 3u;
+ fileKeyboard.dpStoreArray = file_store;
+ EXPECT_EQ(KmnCompilerMessages::ERROR_StoreDoesNotExist, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+
+ // index, two commas and extra parameter, KmnCompilerMessages::ERROR_InvalidIndex
+ u16cpy(str, u"index(b,4,5)");
+ fileKeyboard.cxStoreArray = 3u;
+ fileKeyboard.dpStoreArray = file_store;
+ EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidIndex, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- // index, two-digit parameter, valid
+ // index, two-digit offset, valid
u16cpy(str, u"index(b,42)");
fileKeyboard.cxStoreArray = 3u;
fileKeyboard.dpStoreArray = file_store;
@@ -1126,25 +1145,25 @@ TEST_F(CompilerTest, GetXStringImpl_type_i_test) {
const KMX_WCHAR tstr_index_two_digit_valid[] = { UC_SENTINEL, CODE_INDEX, 2, 42, 0 };
EXPECT_EQ(0, u16cmp(tstr_index_two_digit_valid, tstr));
- // index, comma, non-digit parameter, KmnCompilerMessages::ERROR_InvalidIndex
+ // index, comma, non-digit offset, KmnCompilerMessages::ERROR_InvalidIndex
u16cpy(str, u"index(b,g)");
fileKeyboard.cxStoreArray = 3u;
fileKeyboard.dpStoreArray = file_store;
EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidIndex, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- // index, comma, no parameter, KmnCompilerMessages::ERROR_InvalidIndex
+ // index, comma, no offset, KmnCompilerMessages::ERROR_InvalidIndex
u16cpy(str, u"index(b,)");
fileKeyboard.cxStoreArray = 3u;
fileKeyboard.dpStoreArray = file_store;
EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidIndex, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- // index, space and comma, no parameter, KmnCompilerMessages::ERROR_InvalidIndex
+ // index, space and comma, no offset, KmnCompilerMessages::ERROR_InvalidIndex
u16cpy(str, u"index(b ,)");
fileKeyboard.cxStoreArray = 3u;
fileKeyboard.dpStoreArray = file_store;
EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidIndex, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- // index, comma, no parameter but space, KmnCompilerMessages::ERROR_InvalidIndex
+ // index, comma, no offset but space, KmnCompilerMessages::ERROR_InvalidIndex
u16cpy(str, u"index(b, )");
fileKeyboard.cxStoreArray = 3u;
fileKeyboard.dpStoreArray = file_store;
@@ -1180,7 +1199,7 @@ TEST_F(CompilerTest, GetXStringImpl_type_o_test) {
u16cpy(str, u"outs()");
EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidOuts, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- // outs, space in delimiters (see I11814, I11937, #11910, #11894, #11938)
+ // outs, space in delimiters (see #11814, #11937, #11910, #11894, #11938)
u16cpy(str, u"outs( )");
EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidOuts, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
@@ -1214,15 +1233,13 @@ TEST_F(CompilerTest, GetXStringImpl_type_o_test) {
file_store[1].dpString = (PKMX_WCHAR)u"abc";
u16cpy(str, u"outs( b)");
EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- const KMX_WCHAR tstr_outs_space_before_valid[] = { 'a', 'b', 'c', 0 };
- EXPECT_EQ(0, u16cmp(tstr_outs_space_before_valid, tstr));
+ EXPECT_EQ(0, u16cmp(tstr_outs_valid, tstr));
- // outs, space after store, valid (see I11937, #11938)
+ // outs, space after store, valid (see #11937, #11938)
file_store[1].dpString = (PKMX_WCHAR)u"abc";
u16cpy(str, u"outs(b )");
EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- const KMX_WCHAR tstr_outs_space_after_valid[] = { 'a', 'b', 'c', 0 };
- EXPECT_EQ(0, u16cmp(tstr_outs_space_after_valid, tstr));
+ EXPECT_EQ(0, u16cmp(tstr_outs_valid, tstr));
}
// tests strings starting with 'c'
@@ -1241,6 +1258,95 @@ TEST_F(CompilerTest, GetXStringImpl_type_c_test) {
u16cpy(file_store[1].szName, u"b");
u16cpy(file_store[2].szName, u"c");
+ // are comments stripped before this point?
+ // if so, why the test on whitespace after 'c'?
+
+ // KmnCompilerMessages::ERROR_InvalidToken
+ fileKeyboard.version = VERSION_60;
+ u16cpy(str, u"cde");
+ EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidToken, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+
+ // context, KmnCompilerMessages::ERROR_ContextInVirtualKeySection *** TODO ***
+
+ // context, no offset, valid
+ fileKeyboard.version = VERSION_60;
+ u16cpy(str, u"context");
+ EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+ const KMX_WCHAR tstr_context_no_offset_valid[] = { UC_SENTINEL, CODE_CONTEXT, 0 };
+ EXPECT_EQ(0, u16cmp(tstr_context_no_offset_valid, tstr));
+
+ // context, KmnCompilerMessages::ERROR_InvalidToken, no close delimiter => NULL
+ fileKeyboard.version = VERSION_60;
+ u16cpy(str, u"context(");
+ EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidToken, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+
+ // context, empty delimiters => empty string, valid
+ fileKeyboard.version = VERSION_60;
+ u16cpy(str, u"context()");
+ EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+ const KMX_WCHAR tstr_context_empty_offset_valid[] = { UC_SENTINEL, CODE_CONTEXT, 0 };
+ EXPECT_EQ(0, u16cmp(tstr_context_empty_offset_valid, tstr));
+
+ // context, space in delimiters (see #11814, #11937, #11910, #11894, #11938)
+ fileKeyboard.version = VERSION_60;
+ u16cpy(str, u"context( )");
+ EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+
+ // context, offset, valid
+ fileKeyboard.version = VERSION_60;
+ u16cpy(str, u"context(1)");
+ EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+ const KMX_WCHAR tstr_context_offset_valid[] = { UC_SENTINEL, CODE_CONTEXTEX, 1, 0 };
+ EXPECT_EQ(0, u16cmp(tstr_context_offset_valid, tstr));
+
+ // context, CERR_InvalidToke, offset < 1
+ fileKeyboard.version = VERSION_60;
+ u16cpy(str, u"context(0)");
+ EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidToken, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+
+ // context, large offset < 0xF000, valid
+ fileKeyboard.version = VERSION_60;
+ u16cpy(str, u"context(61439)"); //0xF000 - 1
+ EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+ const KMX_WCHAR tstr_context_large_offset_valid[] = { UC_SENTINEL, CODE_CONTEXTEX, 61439, 0 };
+ EXPECT_EQ(0, u16cmp(tstr_context_large_offset_valid, tstr));
+
+ // context, KmnCompilerMessages::ERROR_InvalidToken, too large offset == 0xF000
+ fileKeyboard.version = VERSION_60;
+ u16cpy(str, u"context(61440)"); //0xF000
+ EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidToken, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+
+ // context, KmnCompilerMessages::ERROR_60FeatureOnly_Contextn
+ fileKeyboard.version = VERSION_50;
+ u16cpy(str, u"context(1)");
+ EXPECT_EQ(KmnCompilerMessages::ERROR_60FeatureOnly_Contextn, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+
+ // context, valid
+ fileKeyboard.version = VERSION_60;
+ u16cpy(str, u"context(1)");
+ EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+ const KMX_WCHAR tstr_context_valid[] = { UC_SENTINEL, CODE_CONTEXTEX, 1, 0 };
+ EXPECT_EQ(0, u16cmp(tstr_context_valid, tstr));
+
+ // context, space before offset, valid
+ fileKeyboard.version = VERSION_60;
+ u16cpy(str, u"context( 1)");
+ EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+ EXPECT_EQ(0, u16cmp(tstr_context_valid, tstr));
+
+ // context, space after offset, valid (see #11937, #11938)
+ fileKeyboard.version = VERSION_60;
+ u16cpy(str, u"context(1 )");
+ EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+ EXPECT_EQ(0, u16cmp(tstr_context_valid, tstr));
+
+ // clearcontext, valid
+ fileKeyboard.version = VERSION_60;
+ u16cpy(str, u"clearcontext");
+ EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+ const KMX_WCHAR tstr_clearcontext_valid[] = { UC_SENTINEL, CODE_CLEARCONTEXT, 0 };
+ EXPECT_EQ(0, u16cmp(tstr_clearcontext_valid, tstr));
+
// call, KmnCompilerMessages::ERROR_501FeatureOnly_Call
fileKeyboard.version = VERSION_50;
u16cpy(str, u"call");
@@ -1258,7 +1364,7 @@ TEST_F(CompilerTest, GetXStringImpl_type_c_test) {
u16cpy(str, u"call()");
EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidCall, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
- // call, space in delimiters (see I11814, I11937, #11910, #11894, #11938)
+ // call, space in delimiters (see #11814, #11937, #11910, #11894, #11938)
fileKeyboard.version = VERSION_501;
u16cpy(str, u"call( )");
EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidCall, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
@@ -1303,7 +1409,7 @@ TEST_F(CompilerTest, GetXStringImpl_type_c_test) {
EXPECT_EQ(0, u16cmp(tstr_call_valid, tstr));
EXPECT_EQ(TSS_CALLDEFINITION, file_store[1].dwSystemID);
- // call, space after store, valid (see I11937, #11938)
+ // call, space after store, valid (see #11937, #11938)
fileKeyboard.version = VERSION_501;
file_store[1].dpString = (PKMX_WCHAR)u"a.dll:A";
file_store[1].dwSystemID = TSS_NONE;
@@ -1313,6 +1419,147 @@ TEST_F(CompilerTest, GetXStringImpl_type_c_test) {
EXPECT_EQ(TSS_CALLDEFINITION, file_store[1].dwSystemID);
}
+// tests strings starting with 'n'
+TEST_F(CompilerTest, GetXStringImpl_type_n_test) {
+ KMX_WCHAR tstr[128];
+ fileKeyboard.version = VERSION_70;
+ KMX_WCHAR str[LINESIZE];
+ KMX_WCHAR output[GLOBAL_BUFSIZE];
+ PKMX_WCHAR newp = nullptr;
+ PFILE_STORE file_store = new FILE_STORE[100];
+ fileKeyboard.cxStoreArray = 3u;
+ fileKeyboard.dpStoreArray = file_store;
+ u16cpy(file_store[0].szName, u"a");
+ u16cpy(file_store[1].szName, u"b");
+ u16cpy(file_store[2].szName, u"c");
+
+ // KmnCompilerMessages::ERROR_InvalidToken
+ fileKeyboard.version = VERSION_70;
+ u16cpy(str, u"nmo");
+ EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidToken, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+
+ // notany, KmnCompilerMessages::ERROR_60FeatureOnly_Contextn
+ fileKeyboard.version = VERSION_60;
+ u16cpy(str, u"notany");
+ EXPECT_EQ(KmnCompilerMessages::ERROR_70FeatureOnly, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+
+ // notany, KmnCompilerMessages::ERROR_AnyInVirtualKeySection *** TODO ***
+
+ // notany, no close delimiter => NULL
+ fileKeyboard.version = VERSION_70;
+ u16cpy(str, u"notany(");
+ EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidAny, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+
+ // notany, empty delimiters => empty string
+ fileKeyboard.version = VERSION_70;
+ u16cpy(str, u"notany()");
+ EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidAny, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+
+ // notany, space in delimiters (see #11814, #11937, #11910, #11894, #11938)
+ fileKeyboard.version = VERSION_70;
+ u16cpy(str, u"notany( )");
+ EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidAny, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+
+ // notany, KmnCompilerMessages::ERROR_StoreDoesNotExist
+ fileKeyboard.version = VERSION_70;
+ u16cpy(str, u"notany(d)");
+ EXPECT_EQ(KmnCompilerMessages::ERROR_StoreDoesNotExist, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+
+ // notany, KmnCompilerMessages::ERROR_StoreDoesNotExist, space before store
+ fileKeyboard.version = VERSION_70;
+ u16cpy(str, u"notany( d)");
+ EXPECT_EQ(KmnCompilerMessages::ERROR_StoreDoesNotExist, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+
+ // notany, KmnCompilerMessages::ERROR_StoreDoesNotExist, space after store
+ fileKeyboard.version = VERSION_70;
+ u16cpy(str, u"notany(d )");
+ EXPECT_EQ(KmnCompilerMessages::ERROR_StoreDoesNotExist, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+
+ // notany, valid
+ fileKeyboard.version = VERSION_70;
+ u16cpy(str, u"notany(b)");
+ file_store[1].dpString = (PKMX_WCHAR)u"abc"; // non-empty
+ EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+ const KMX_WCHAR tstr_notany_valid[] = { UC_SENTINEL, CODE_NOTANY, 2, 0 };
+ EXPECT_EQ(0, u16cmp(tstr_notany_valid, tstr));
+
+ // notany, valid, empy store
+ fileKeyboard.version = VERSION_70;
+ u16cpy(str, u"notany(b)");
+ file_store[1].dpString = (PKMX_WCHAR)u""; // empty
+ EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+ EXPECT_EQ(0, u16cmp(tstr_notany_valid, tstr));
+
+ // notany, space before store, valid
+ fileKeyboard.version = VERSION_70;
+ u16cpy(str, u"notany( b)");
+ file_store[1].dpString = (PKMX_WCHAR)u"abc"; // non-empty
+ EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+ EXPECT_EQ(0, u16cmp(tstr_notany_valid, tstr));
+
+ // notany, space after store, valid (see #11937, #11938)
+ fileKeyboard.version = VERSION_70;
+ u16cpy(str, u"notany(b )");
+ file_store[1].dpString = (PKMX_WCHAR)u"abc"; // non-empty
+ EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+ EXPECT_EQ(0, u16cmp(tstr_notany_valid, tstr));
+
+ // null
+ fileKeyboard.version = VERSION_70;
+ u16cpy(str, u"nul");
+ EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+ const KMX_WCHAR tstr_null_valid[] = { UC_SENTINEL, CODE_NUL, 0 };
+ EXPECT_EQ(0, u16cmp(tstr_null_valid, tstr));
+}
+
+// tests strings starting with 'u'
+TEST_F(CompilerTest, GetXStringImpl_type_u_test) {
+ KMX_WCHAR tstr[128];
+ fileKeyboard.version = VERSION_70;
+ KMX_WCHAR str[LINESIZE];
+ KMX_WCHAR output[GLOBAL_BUFSIZE];
+ PKMX_WCHAR newp = nullptr;
+
+ // KmnCompilerMessages::ERROR_InvalidToken
+ u16cpy(str, u"uvw");
+ EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidToken, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+
+ // unicode, n1 and n2, valid
+ u16cpy(str, u"u+10330"); // Gothic A
+ EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, TRUE));
+ const KMX_WCHAR tstr_unicode_valid[] = { 0xD800, 0xDF30, 0 }; // see UTF32ToUTF16
+ EXPECT_EQ(0, u16cmp(tstr_unicode_valid, tstr));
+ EXPECT_EQ(0, msgproc_errors.size());
+
+ // unicode, ERROR_InvalidValue
+ u16cpy(str, u"u+10330z"); // Gothic A, unexpected character 'z'
+ EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidValue, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, TRUE));
+
+ // unicode, space after, valid
+ u16cpy(str, u"u+10330 "); // Gothic A
+ EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, TRUE));
+ EXPECT_EQ(0, u16cmp(tstr_unicode_valid, tstr));
+ EXPECT_EQ(0, msgproc_errors.size());
+
+ // unicode, KmnCompilerMessages::ERROR_InvalidCharacter
+ u16cpy(str, u"u+110000");
+ EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidCharacter, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, TRUE));
+
+ // unicode, n1 only, valid
+ u16cpy(str, u"u+0061"); // a
+ EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, TRUE));
+ const KMX_WCHAR tstr_unicode_n1_only_valid[] = { 0x0061, 0 }; // see UTF32ToUTF16
+ EXPECT_EQ(0, u16cmp(tstr_unicode_n1_only_valid, tstr));
+ EXPECT_EQ(0, msgproc_errors.size());
+
+ // unicode, n1 and n2, valid, KmnCompilerMessages::WARN_UnicodeInANSIGroup
+ u16cpy(str, u"u+10330"); // Gothic A
+ EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
+ EXPECT_EQ(0, u16cmp(tstr_unicode_valid, tstr));
+ EXPECT_EQ(1, msgproc_errors.size());
+ EXPECT_EQ(KmnCompilerMessages::WARN_UnicodeInANSIGroup, msgproc_errors[0].errorCode);
+}
+
// KMX_DWORD process_baselayout(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx)
// KMX_DWORD process_platform(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx)
// KMX_DWORD process_if_synonym(KMX_DWORD dwSystemID, PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx)
diff --git a/developer/src/kmcmplib/tests/gtest-km_u16-test.cpp b/developer/src/kmcmplib/tests/gtest-km_u16-test.cpp
index b67957bd317..75d74607cd4 100644
--- a/developer/src/kmcmplib/tests/gtest-km_u16-test.cpp
+++ b/developer/src/kmcmplib/tests/gtest-km_u16-test.cpp
@@ -166,3 +166,91 @@ TEST(km_u16_Test, u16tok_str_compare_to_strtok) {
EXPECT_TRUE(!strcmp("ghi", strtok(nullptr, reverse_delim)));
EXPECT_EQ(nullptr, strtok(nullptr, reverse_delim));
}
+
+TEST(km_u16_Test, u16ltrim) {
+ KMX_WCHAR str[LINESIZE];
+ PKMX_WCHAR q;
+
+ EXPECT_TRUE(!u16ltrim(nullptr));
+
+ std::map m{
+ // input output
+ {u"", u"" },
+ {u" ", u"" },
+ {u" ", u"" },
+ {u"abc", u"abc" },
+ {u" abc", u"abc" },
+ {u" abc", u"abc" },
+ {u"abc ", u"abc " },
+ {u"\tabc", u"abc" },
+ {u"abc def", u"abc def"},
+ {u" abc def", u"abc def"},
+ };
+
+ for (auto i = m.begin(); i != m.end(); i++) {
+ u16cpy(str, i->first);
+ q = u16ltrim(str);
+ EXPECT_TRUE(!u16cmp(i->second, q));
+ EXPECT_EQ(str, q);
+ }
+}
+
+TEST(km_u16_Test, u16rtrim) {
+ KMX_WCHAR str[LINESIZE];
+ PKMX_WCHAR q;
+
+ EXPECT_TRUE(!u16rtrim(nullptr));
+
+ std::map m{
+ // input output
+ {u"", u"" },
+ {u" ", u"" },
+ {u" ", u"" },
+ {u"abc", u"abc" },
+ {u"abc ", u"abc" },
+ {u"abc ", u"abc" },
+ {u" abc", u" abc" },
+ {u"abc\t", u"abc" },
+ {u"abc def", u"abc def"},
+ {u"abc def ", u"abc def"},
+ };
+
+ for (auto i = m.begin(); i != m.end(); i++) {
+ u16cpy(str, i->first);
+ q = u16rtrim(str);
+ EXPECT_TRUE(!u16cmp(i->second, q));
+ EXPECT_EQ(str, q);
+ }
+}
+
+TEST(km_u16_Test, u16trim) {
+ KMX_WCHAR str[LINESIZE];
+ PKMX_WCHAR q;
+
+ EXPECT_TRUE(!u16trim(nullptr));
+
+ std::map m{
+ // input output
+ {u"", u"" },
+ {u" ", u"" },
+ {u" ", u"" },
+ {u"abc", u"abc" },
+ {u"abc ", u"abc" },
+ {u"abc ", u"abc" },
+ {u" abc", u"abc" },
+ {u" abc", u"abc" },
+ {u" abc ", u"abc" },
+ {u" abc ", u"abc" },
+ {u"abc\t", u"abc" },
+ {u"\tabc", u"abc" },
+ {u"abc def", u"abc def" },
+ {u" abc def ", u"abc def"},
+ };
+
+ for (auto i = m.begin(); i != m.end(); i++) {
+ u16cpy(str, i->first);
+ q = u16trim(str);
+ EXPECT_TRUE(!u16cmp(i->second, q));
+ EXPECT_EQ(str, q);
+ }
+}
diff --git a/developer/src/packages.inc.sh b/developer/src/packages.inc.sh
index 28f2513f974..0726209973a 100644
--- a/developer/src/packages.inc.sh
+++ b/developer/src/packages.inc.sh
@@ -6,7 +6,6 @@
readonly PACKAGES=(
common/web/keyman-version
common/web/types
- common/models/types
core/include/ldml
developer/src/common/web/utils
developer/src/kmc-analyze
diff --git a/developer/src/server/package.json b/developer/src/server/package.json
index f3be3425f2a..8619adceecc 100644
--- a/developer/src/server/package.json
+++ b/developer/src/server/package.json
@@ -12,7 +12,7 @@
"@keymanapp/developer-utils": "*",
"@sentry/node": "^7.57.0",
"chalk": "^4.1.2",
- "express": "^4.19.2",
+ "express": "^4.20.0",
"multer": "^1.4.5-lts.1",
"ngrok": "^5.0.0-beta.2",
"open": "^8.4.0",
diff --git a/developer/src/tike/child/UfrmKeymanWizard.pas b/developer/src/tike/child/UfrmKeymanWizard.pas
index baa906e8369..59e0f8a3411 100644
--- a/developer/src/tike/child/UfrmKeymanWizard.pas
+++ b/developer/src/tike/child/UfrmKeymanWizard.pas
@@ -3263,6 +3263,14 @@ procedure TfrmKeymanWizard.cmdImportFromOnScreenClick(Sender: TObject); // I39
Exit;
end;
+ if MessageDlg(
+ 'Importing the Desktop On Screen Keyboard will overwrite all changes in all '+
+ 'layers in the touch layout. Continue and overwrite touch layout?', mtWarning,
+ mbOkCancel, 0) = mrCancel then
+ begin
+ Exit;
+ end;
+
if Self.Modified then // I4059
begin
if not FKeymanDeveloperOptions.OSKAutoSaveBeforeImporting then
diff --git a/developer/src/tike/xml/help/contexthelp.xml b/developer/src/tike/xml/help/contexthelp.xml
index 2298b5676e7..66140f0ae97 100644
--- a/developer/src/tike/xml/help/contexthelp.xml
+++ b/developer/src/tike/xml/help/contexthelp.xml
@@ -920,7 +920,151 @@
-
+
+
+
+
+ Below Configure Server, untick the box, and it will quit displaying any local URLs for keyboard testing.
+
+
+