Merge pull request #12258 from keymanapp/chore/merge-master-into-windows-updates
chore: merge master into windows-updates
116
HISTORY.md
|
|
@ -1,5 +1,111 @@
|
|||
# Keyman Version History
|
||||
|
||||
## 18.0.94 alpha 2024-08-21
|
||||
|
||||
* fix(core): look for `emcc` instead of `emcc.py` (#12235)
|
||||
* fix(web): improve tokenization output when wordbreaker breaks spec for span properties in output (#12229)
|
||||
* chore(android): Use RTL-aware alignment and padding for layouts (#12225)
|
||||
* fix(web): disable fat-finger data use when mayCorrect = false (#12220)
|
||||
* fix(android): Auto-mirror back and forward arrows for RTL support (#12227)
|
||||
* feat(android): Add localization for Arabic (#12228)
|
||||
* fix(android): Auto-mirror increment and decrement arrows for RTL support (#12230)
|
||||
|
||||
## 18.0.93 alpha 2024-08-20
|
||||
|
||||
* (#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)
|
||||
|
||||
## 18.0.92 alpha 2024-08-19
|
||||
|
||||
* chore(deps-dev): bump @75lb/deep-merge from 1.1.1 to 1.1.2 (#12118)
|
||||
* chore(deps): bump semver from 7.5.4 to 7.6.0 (#12119)
|
||||
* fix(windows): "Keyman" is not localized in UI strings (#12162)
|
||||
* feat(android): Enhance how ENTER key is handled in apps (#12125)
|
||||
* refactor(web): move `lm-worker` → `worker-thread` (#12150)
|
||||
* fix(developer): remove redundant check in LdmlKeyboardCompiler.validate() (#11858)
|
||||
|
||||
## 18.0.91 alpha 2024-08-16
|
||||
|
||||
* refactor(web): move `predictive-text` → `worker-main` (#12146)
|
||||
* fix(web): restore flick functionality (#12187)
|
||||
* refactor(web): move `lm-message-types` → `predictive-text/types` (#12149)
|
||||
* fix(developer): enforce presence of kps Info.Description field in info compilers (#12204)
|
||||
* fix(developer): enforce presence of Version field when FollowKeyboardVersion is not set, in package compiler (#12205)
|
||||
|
||||
## 18.0.90 alpha 2024-08-15
|
||||
|
||||
* refactor(web): move parts of `keyboard-processor` → `js-processor` (#12111)
|
||||
* fix(web): allow `lm-worker` to build on Linux (#12181)
|
||||
* refactor(web): move remaining parts of `keyboard-processor` → `keyboard` (#12131)
|
||||
* docs: update .kmx documentation around bitmaps, modifier state (#12183)
|
||||
* refactor(web): rename `package-cache` → `keyboard-storage` (#12135)
|
||||
|
||||
## 18.0.89 alpha 2024-08-14
|
||||
|
||||
* feat(web): test skipped prediction round handling (#12169)
|
||||
* fix(web): support live configuration of longpress delay (#12175)
|
||||
* feat(web): add osk.gestureParams for better gesture-config persistence (#12176)
|
||||
* refactor(core): move utfcodec to common (#12171)
|
||||
* refactor: move kmx_u16 to common and rename to km_u16 (#12177)
|
||||
* refactor(developer): use npm or local source for server addons instead of github references (#12090)
|
||||
* fix(developer): fix crash with Windows Clipboard by ignoring zero scan code in debugger (#12166)
|
||||
* chore(developer): update SIL logo (#12168)
|
||||
|
||||
## 18.0.88 alpha 2024-08-13
|
||||
|
||||
* docs: add .kmx specification (#12163)
|
||||
|
||||
## 18.0.87 alpha 2024-08-12
|
||||
|
||||
* chore(web): drop flaky auto-test component (#12155)
|
||||
* fix(common): show description even if child projects specifies path (#12145)
|
||||
|
||||
## 18.0.86 alpha 2024-08-09
|
||||
|
||||
* chore(android): Update splash screen with SIL Tai Heritage Pro logo (#12127)
|
||||
* chore(common): Update localization for Greek Polytonic (#12112)
|
||||
* chore(common): add support for build target platform exclusions (#12113)
|
||||
* feat(common): add top-level build.sh (#12114)
|
||||
* chore(developer): start building Developer on linux,mac (#12117)
|
||||
* refactor(web): Move `common/web/recorder` → `web/src/tools/testing/recorder-core` (#12092)
|
||||
|
||||
## 18.0.85 alpha 2024-08-08
|
||||
|
||||
* fix(developer): update date for last git commit date fixture (#12122)
|
||||
* refactor(android/engine): Parse keyboards.json for FirstVoices app (#11943)
|
||||
|
||||
## 18.0.84 alpha 2024-08-07
|
||||
|
||||
* refactor(common): move kpj-related files into developer-utils (#11531)
|
||||
* refactor(common): move kps-file.ts to @keymanapp/developer-utils (#11763)
|
||||
* refactor(common): move kvks-file to @keymanapp/developer-utils (#11764)
|
||||
* refactor(common): move .keyman-touch-layout reader/writer to @keymanapp/developer-utils (#11765)
|
||||
* refactor(common): move LDML keyboard .xml reader/writer and kmx-plus builder to @keymanapp/developer-utils (#12081)
|
||||
* refactor(common): move compiler-interfaces to @keymanapp/developer-utils (#12088)
|
||||
* refactor(common): move xml2js and related deps to @keymanapp/developer-utils (#12101)
|
||||
|
||||
## 18.0.83 alpha 2024-08-06
|
||||
|
||||
* chore(common): Update history from 17.0.328 (#12093)
|
||||
* change(common/models): change model tokenization to also tokenize whitespace (#11975)
|
||||
* feat(web): support transform tokenization when given a root context (#11998)
|
||||
* change(web): track whitespace-aware tokenization for context + correction-search caching (#11979)
|
||||
* change(web): leverage tokenization to preserve punctuation and whitespace when predicting (#11997)
|
||||
* fix(web): patch up worker build to provide artifacts for its tests (#12082)
|
||||
* chore(web): move `web/src/engine/paths/` → `web/src/engine/interfaces/` (#12064)
|
||||
* chore(web): move `common/web/input-processor/` → `web/src/engine/main/` (#12066)
|
||||
* refactor(web): refactor and harmonize constants (#12072)
|
||||
* chore(common): add data versions to minimum-versions.inc.sh (#12103)
|
||||
* fix(web): fix lm-worker test broken by botched merge conflict resolution (#12104)
|
||||
|
||||
## 18.0.82 alpha 2024-08-05
|
||||
|
||||
* chore(windows): remove QIT_VSHIFTDOWN QIT _VSHIFTUP (#11973)
|
||||
* chore(developer): add language/reference (#11799)
|
||||
|
||||
## 18.0.81 alpha 2024-08-03
|
||||
|
||||
* chore(developer): Revert "chore(developer): remove redundant references from tsconfig.json" (#12076)
|
||||
|
|
@ -138,7 +244,7 @@
|
|||
|
||||
## 18.0.63 alpha 2024-06-26
|
||||
|
||||
* feat(linux): implement Linux side of SimulateAltGr option :checkered_flag: (#11852)
|
||||
* feat(linux): implement Linux side of SimulateAltGr option (#11852)
|
||||
|
||||
## 18.0.62 alpha 2024-06-25
|
||||
|
||||
|
|
@ -150,8 +256,8 @@
|
|||
|
||||
* feat(web): optimization via lazy preprocessing of keyboard touch-layout info (#11265)
|
||||
* fix(android): clear globe highlight when displaying keyboard picker (#11826)
|
||||
* refactor(linux): add KeymanOption class for options :checkered_flag: (#11850)
|
||||
* refactor(linux): rename methods that deal with keyboard options :checkered_flag: (#11851)
|
||||
* refactor(linux): add KeymanOption class for options (#11850)
|
||||
* refactor(linux): rename methods that deal with keyboard options (#11851)
|
||||
|
||||
## 18.0.60 alpha 2024-06-21
|
||||
|
||||
|
|
@ -566,6 +672,10 @@
|
|||
* chore(common): move to 18.0 alpha (#10713)
|
||||
* chore: move to 18.0 alpha
|
||||
|
||||
## 17.0.328 stable 2024-07-27
|
||||
|
||||
* fix(web): add nullish test in setOsk (#12041)
|
||||
|
||||
## 17.0.327 stable 2024-07-25
|
||||
|
||||
* fix(android): include DOMRect polyfill for older ES6-supporting devices (#11654)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# License
|
||||
|
||||
Copyright (c) 2017-2022 SIL International. All rights reserved.
|
||||
Copyright (c) 2017-2024 SIL Global. All rights reserved.
|
||||
|
||||
Licensed under the MIT License.
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ Licensed under the MIT License.
|
|||
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2017-2022 SIL International
|
||||
Copyright (c) 2017-2024 SIL Global
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
# License
|
||||
|
||||
Copyright (c) SIL International.
|
||||
Copyright (c) SIL Global.
|
||||
|
||||
Keyman is an open source project distributed under the [MIT license](LICENSE.md).
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
18.0.82
|
||||
18.0.95
|
||||
|
|
@ -297,6 +297,11 @@
|
|||
android:configChanges="orientation"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme.Base" />
|
||||
<activity
|
||||
android:name=".AdjustLongpressDelayActivity"
|
||||
android:configChanges="orientation"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme.Base" />
|
||||
|
||||
<!-- Put other WebViewActivities in a separate process so the Keyboard WebView doesn't lag.
|
||||
Ref https://stackoverflow.com/questions/40650643/timed-out-waiting-on-iinputcontextcallback-with-custom-keyboard-on-android -->
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
package com.keyman.android;
|
||||
|
||||
import com.tavultesoft.kmapro.AdjustLongpressDelayActivity;
|
||||
import com.tavultesoft.kmapro.BuildConfig;
|
||||
import com.tavultesoft.kmapro.DefaultLanguageResource;
|
||||
import com.tavultesoft.kmapro.KeymanSettingsActivity;
|
||||
|
|
@ -175,6 +176,9 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
|
|||
}
|
||||
}
|
||||
|
||||
// Determine special handling for ENTER key
|
||||
KMManager.setEnterMode(attribute.imeOptions, inputType);
|
||||
|
||||
InputConnection ic = getCurrentInputConnection();
|
||||
if (ic != null) {
|
||||
ExtractedText icText = ic.getExtractedText(new ExtractedTextRequest(), 0);
|
||||
|
|
@ -244,6 +248,8 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
|
|||
if (exText != null)
|
||||
exText = null;
|
||||
}
|
||||
// Initialize keyboard options
|
||||
KMManager.sendOptionsToKeyboard();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*
|
||||
* (Optional description of this file)
|
||||
*/
|
||||
package com.tavultesoft.kmapro;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.keyman.engine.BaseActivity;
|
||||
import com.keyman.engine.KMManager;
|
||||
|
||||
/**
|
||||
* Settings menu for adjusting the longpress delay time. The value for the current longpress delay time
|
||||
* is saved in shared preferences as an integer (milliseconds).
|
||||
*/
|
||||
public class AdjustLongpressDelayActivity extends BaseActivity {
|
||||
private static final String TAG = "AdjustLongpressDelay";
|
||||
public static final String adjustLongpressDelayKey = "AdjustLongpressDelay";
|
||||
|
||||
// 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
|
||||
|
||||
/**
|
||||
* Convert currentDelayTimeMS to progress
|
||||
* @return int
|
||||
*/
|
||||
private int delayTimeToProgress() {
|
||||
return (currentDelayTimeMS / delayTimeIncrement) - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert progress to currentDelayTimeMS
|
||||
* @param progress
|
||||
* @return int (milliseconds)
|
||||
*/
|
||||
private int progressToDelayTime(int progress) {
|
||||
return (progress + 1) * delayTimeIncrement + 100;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final Context context = this;
|
||||
|
||||
setContentView(R.layout.activity_adjust_longpress_delay);
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.titlebar);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setTitle(null);
|
||||
actionBar.setDisplayUseLogoEnabled(false);
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setDisplayShowHomeEnabled(true);
|
||||
actionBar.setDisplayShowTitleEnabled(false);
|
||||
actionBar.setDisplayShowCustomEnabled(true);
|
||||
actionBar.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.keyman_blue)));
|
||||
}
|
||||
|
||||
TextView adjustLongpressDelayActivityTitle = (TextView) findViewById(R.id.bar_title);
|
||||
|
||||
String titleStr = getString(R.string.adjust_longpress_delay);
|
||||
adjustLongpressDelayActivityTitle.setTextColor(ContextCompat.getColor(this, R.color.ms_white));
|
||||
adjustLongpressDelayActivityTitle.setText(titleStr);
|
||||
|
||||
currentDelayTimeMS = KMManager.getLongpressDelay();
|
||||
|
||||
TextView adjustLongpressDelayText = (TextView) findViewById(R.id.delayTimeText);
|
||||
String longpressDelayTextSeconds = String.format(getString(R.string.longpress_delay_time), (float)(currentDelayTimeMS/1000.0));
|
||||
adjustLongpressDelayText.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
|
||||
adjustLongpressDelayText.setText(longpressDelayTextSeconds);
|
||||
|
||||
final SeekBar seekBar = (SeekBar) findViewById(R.id.seekBar);
|
||||
seekBar.setProgress(delayTimeToProgress());
|
||||
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
// Update the text field.
|
||||
// The keyboard options will be saved and sent to KeymanWeb when exiting the menu
|
||||
currentDelayTimeMS = progressToDelayTime(progress);
|
||||
String longpressDelayTextSeconds = String.format(getString(R.string.longpress_delay_time), (float)(currentDelayTimeMS/1000.0));
|
||||
adjustLongpressDelayText.setText(longpressDelayTextSeconds);
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.delayTimeDownButton).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (currentDelayTimeMS > minLongpressTime) {
|
||||
currentDelayTimeMS -= delayTimeIncrement;
|
||||
seekBar.setProgress(delayTimeToProgress());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.delayTimeUpButton).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (currentDelayTimeMS < maxLongpressTime) {
|
||||
currentDelayTimeMS += delayTimeIncrement;
|
||||
seekBar.setProgress(delayTimeToProgress());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Store the longpress delay as a reference
|
||||
// and then update KeymanWeb with the longpress delay
|
||||
KMManager.setLongpressDelay(currentDelayTimeMS);
|
||||
KMManager.sendOptionsToKeyboard();
|
||||
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSupportNavigateUp() {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -105,7 +105,7 @@ public final class KeyboardSettingsActivity extends AppCompatActivity {
|
|||
hashMap = new HashMap<>();
|
||||
final String customHelpLink = kbd.getHelpLink();
|
||||
// Check if app declared FileProvider
|
||||
icon = String.valueOf(R.drawable.ic_arrow_forward);
|
||||
icon = String.valueOf(R.drawable.ic_action_forward);
|
||||
// Don't show help link arrow if File Provider unavailable, or custom help doesn't exist
|
||||
if ( (customHelpLink != null && !FileProviderUtils.exists(context)) ||
|
||||
(customHelpLink == null && !packageID.equals(KMManager.KMDefault_UndefinedPackageID)) ) {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat {
|
|||
private static Context context;
|
||||
|
||||
private Preference languagesPreference, installKeyboardOrDictionary, displayLanguagePreference,
|
||||
adjustKeyboardHeight;
|
||||
adjustKeyboardHeight, adjustLongpressDelay;
|
||||
private ListPreference spacebarTextPreference;
|
||||
private CheckBoxPreference setSystemKeyboardPreference;
|
||||
private CheckBoxPreference setDefaultKeyboardPreference;
|
||||
|
|
@ -99,6 +99,7 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat {
|
|||
});
|
||||
|
||||
setDefaultKeyboardPreference.setOnPreferenceChangeListener(checkBlocker);
|
||||
|
||||
adjustKeyboardHeight = new Preference(context);
|
||||
adjustKeyboardHeight.setKey(AdjustKeyboardHeightActivity.adjustKeyboardHeightKey);
|
||||
adjustKeyboardHeight.setTitle(getString(R.string.adjust_keyboard_height));
|
||||
|
|
@ -106,6 +107,13 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat {
|
|||
Intent adjustKeyboardHeightIntent = new Intent(context, AdjustKeyboardHeightActivity.class);
|
||||
adjustKeyboardHeight.setIntent(adjustKeyboardHeightIntent);
|
||||
|
||||
adjustLongpressDelay = new Preference(context);
|
||||
adjustLongpressDelay.setKey(AdjustLongpressDelayActivity.adjustLongpressDelayKey);
|
||||
adjustLongpressDelay.setTitle(getString(R.string.adjust_longpress_delay));
|
||||
adjustLongpressDelay.setWidgetLayoutResource(R.layout.preference_duration_icon_layout);
|
||||
Intent adjustLongpressDelayIntent = new Intent(context, AdjustLongpressDelayActivity.class);
|
||||
adjustLongpressDelay.setIntent(adjustLongpressDelayIntent);
|
||||
|
||||
/* Spacebar Caption Preference */
|
||||
|
||||
spacebarTextPreference = new ListPreference(context);
|
||||
|
|
@ -188,8 +196,6 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat {
|
|||
sendCrashReportPreference.setSummaryOff(getString(R.string.show_send_crash_report_off));
|
||||
sendCrashReportPreference.setDefaultValue(true);
|
||||
|
||||
|
||||
|
||||
screen.addPreference(languagesPreference);
|
||||
screen.addPreference(installKeyboardOrDictionary);
|
||||
screen.addPreference(displayLanguagePreference);
|
||||
|
|
@ -197,6 +203,7 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat {
|
|||
screen.addPreference(setDefaultKeyboardPreference);
|
||||
|
||||
screen.addPreference(adjustKeyboardHeight);
|
||||
screen.addPreference(adjustLongpressDelay);
|
||||
screen.addPreference(spacebarTextPreference);
|
||||
|
||||
screen.addPreference(hapticFeedbackPreference);
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ public final class LanguageSettingsActivity extends AppCompatActivity {
|
|||
updateActiveLexicalModel();
|
||||
|
||||
ImageView imageView = (ImageView) layout.findViewById(R.id.image1);
|
||||
imageView.setImageResource(R.drawable.ic_arrow_forward);
|
||||
imageView.setImageResource(R.drawable.ic_action_forward);
|
||||
layout.setEnabled(true);
|
||||
layout.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
|
|
@ -198,7 +198,7 @@ public final class LanguageSettingsActivity extends AppCompatActivity {
|
|||
* textView = (TextView) layout.findViewById(R.id.text1);
|
||||
* textView.setText(getString(R.string.manage_dictionary));
|
||||
* imageView = (ImageView) layout.findViewById(R.id.image1);
|
||||
* imageView.setImageResource(R.drawable.ic_arrow_forward);
|
||||
* imageView.setImageResource(R.drawable.ic_action_forward);
|
||||
*/
|
||||
|
||||
listView.setAdapter(adapter);
|
||||
|
|
@ -346,7 +346,7 @@ public final class LanguageSettingsActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
holder.text.setText(kbd.getResourceName());
|
||||
holder.img.setImageResource(R.drawable.ic_arrow_forward);
|
||||
holder.img.setImageResource(R.drawable.ic_action_forward);
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ public final class LanguagesSettingsActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
holder.textLang.setText(data.name);
|
||||
holder.img.setImageResource(R.drawable.ic_arrow_forward);
|
||||
holder.img.setImageResource(R.drawable.ic_action_forward);
|
||||
|
||||
holder.textCount.setText(getContext().getResources().getQuantityString(R.plurals.keyboard_count, data.keyboards.size(), data.keyboards.size()));
|
||||
|
||||
|
|
|
|||
|
|
@ -472,7 +472,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
|
||||
@Override
|
||||
public void onKeyboardLoaded(KeyboardType keyboardType) {
|
||||
// Do nothing
|
||||
// Initialize keyboard options
|
||||
KMManager.sendOptionsToKeyboard();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -646,7 +647,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
final View textSizeController = inflater.inflate(R.layout.text_size_controller, null);
|
||||
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(MainActivity.this);
|
||||
dialogBuilder.setIcon(R.drawable.ic_light_action_textsize);
|
||||
dialogBuilder.setTitle(String.format(getString(R.string.text_size), textSize));
|
||||
dialogBuilder.setTitle(getTextSizeString());
|
||||
dialogBuilder.setView(textSizeController);
|
||||
dialogBuilder.setPositiveButton(getString(R.string.label_ok), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
|
|
@ -677,7 +678,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
textSize = progress + minTextSize;
|
||||
textView.setTextSize((float) textSize);
|
||||
dialog.setTitle(String.format(getString(R.string.text_size), textSize));
|
||||
dialog.setTitle(getTextSizeString());
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -702,6 +703,16 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine a localized string for "Text Size" plus Arabic numerals
|
||||
* @return String
|
||||
*/
|
||||
private String getTextSizeString() {
|
||||
// Instead of formatting the number, will truncate formatting and concat the actual textSize
|
||||
String label = getString(R.string.text_size).replace("%1$d", "");
|
||||
return label + KMString.format(" %d", textSize);
|
||||
}
|
||||
|
||||
private void showClearTextDialog() {
|
||||
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(MainActivity.this);
|
||||
dialogBuilder.setIcon(R.drawable.ic_light_action_trash);
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ public final class SelectPackageActivity extends AppCompatActivity {
|
|||
HashMap<String, String> hashMap = new HashMap<>();
|
||||
hashMap.put(titleKey, keyboardName);
|
||||
hashMap.put(subtitleKey, pkgID);
|
||||
String icon = String.valueOf(R.drawable.ic_arrow_forward);
|
||||
String icon = String.valueOf(R.drawable.ic_action_forward);
|
||||
hashMap.put(iconKey, icon);
|
||||
list.add(hashMap);
|
||||
}
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 5 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
<item>
|
||||
<bitmap
|
||||
android:src="@drawable/sil_logo_blue_2017_1"
|
||||
android:src="@drawable/sil_logo_color_tai_heritage_pro"
|
||||
android:gravity="center"/>
|
||||
</item>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:src="@drawable/ic_navigation_back"
|
||||
android:tint="@color/keyman_blue" />
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:src="@drawable/ic_light_dialog_textsize_down"
|
||||
android:tint="@android:color/black"
|
||||
android:autoMirrored="true" />
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:src="@drawable/ic_light_dialog_textsize_up"
|
||||
android:tint="@android:color/black"
|
||||
android:autoMirrored="true"/>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M16.24,7.76C15.07,6.59 13.54,6 12,6v6l-4.24,4.24c2.34,2.34 6.14,2.34 8.49,0 2.34,-2.34 2.34,-6.14 -0.01,-8.48zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
|
||||
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include layout="@layout/titlebar" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:paddingBottom="25dp"
|
||||
android:paddingTop="60dp" >
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/delayTimeDownButton"
|
||||
style="?android:attr/actionButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:contentDescription="@string/ic_delay_time_down"
|
||||
android:src="@drawable/ic_light_dialog_textsize_down" />
|
||||
|
||||
<!-- Seekbar range 0 to 6 corresponds to longpress duration 300 ms to 1500 ms -->
|
||||
<SeekBar
|
||||
android:id="@+id/seekBar"
|
||||
android:layout_width="180dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/ic_longpress_delay_slider"
|
||||
android:progress="1"
|
||||
android:max="6"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/delayTimeUpButton"
|
||||
style="?android:attr/actionButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:contentDescription="@string/ic_delay_time_up"
|
||||
android:src="@drawable/ic_light_dialog_textsize_up" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/delayTimeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/linearLayout"
|
||||
android:gravity="center_horizontal"
|
||||
android:layout_marginStart="@dimen/activity_horizontal_margin"
|
||||
android:text="@string/longpress_delay_time"
|
||||
android:textSize="@dimen/longpress_delay_label_textsize"
|
||||
/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
|
@ -19,11 +19,12 @@
|
|||
<ImageButton
|
||||
android:id="@+id/back_button"
|
||||
style="?android:attr/actionButtonStyle"
|
||||
app:tint="@color/image_button_color"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:contentDescription="@string/label_back"
|
||||
android:src="@drawable/ic_navigation_back" />
|
||||
android:src="@drawable/ic_action_back" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/forward_button"
|
||||
|
|
@ -32,7 +33,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/label_forward"
|
||||
android:src="@drawable/ic_navigation_forward" />
|
||||
android:src="@drawable/ic_action_forward" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/close_button"
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
android:layout_marginEnd="5dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/image_button"
|
||||
android:src="@drawable/ic_navigation_back" />
|
||||
android:src="@drawable/ic_action_back" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/forwardButton"
|
||||
|
|
@ -45,7 +45,7 @@
|
|||
android:layout_marginEnd="5dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/image_button"
|
||||
android:src="@drawable/ic_navigation_forward" />
|
||||
android:src="@drawable/ic_action_forward" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/bookmarksButton"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="@dimen/preference_icon"
|
||||
android:layout_height="@dimen/preference_icon"
|
||||
android:src="@drawable/ic_action_timelapse" />
|
||||
|
|
@ -2,4 +2,4 @@
|
|||
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="@dimen/preference_icon"
|
||||
android:layout_height="@dimen/preference_icon"
|
||||
android:src="@drawable/ic_arrow_forward" />
|
||||
android:src="@drawable/ic_action_forward" />
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
android:layout_marginStart="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:contentDescription="@string/ic_text_size_down"
|
||||
android:src="@drawable/ic_light_dialog_textsize_down" />
|
||||
android:src="@drawable/ic_action_decrement" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seekBar"
|
||||
|
|
@ -32,6 +32,6 @@
|
|||
android:layout_marginStart="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:contentDescription="@string/ic_text_size_up"
|
||||
android:src="@drawable/ic_light_dialog_textsize_up" />
|
||||
android:src="@drawable/ic_action_increment" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
android:layout_height="match_parent"
|
||||
android:layout_margin="3dp"
|
||||
android:padding="4dp"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_above="@+id/progressBar"
|
||||
android:weightSum="2"
|
||||
|
|
@ -76,7 +75,6 @@
|
|||
style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/keyman_bar_height"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:visibility="visible"
|
||||
|
|
|
|||
168
android/KMAPro/kMAPro/src/main/res/values-ar-rSA/strings.xml
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- app_name removed in 16.0 beta. Set in build.gradle -->
|
||||
<!-- Context: Menu Action -->
|
||||
<string name="action_share" comment="Menu action to send text content to another app">مشاركة</string>
|
||||
<!-- Context: Menu Action -->
|
||||
<string name="action_web" comment="Menu action to open Keyman browser">متصفح ويب</string>
|
||||
<!-- Context: Menu Action -->
|
||||
<string name="action_text_size" comment="Menu action to adjust text size">حجم النص</string>
|
||||
<!-- Context: Menu Action -->
|
||||
<string name="action_overflow" comment="Show additional menu items">المزيد</string>
|
||||
<!-- Context: Menu Action -->
|
||||
<string name="action_clear_text" comment="Menu action to erase text">محو النص</string>
|
||||
<!-- Context: Menu Action -->
|
||||
<string name="action_info" comment="Menu action to display Keyman help page">معلومات</string>
|
||||
<!-- Context: Menu Action -->
|
||||
<string name="action_settings" comment="Menu action to open Settings menu">الإعدادات</string>
|
||||
<!-- Context: Menu Action -->
|
||||
<string name="action_install_updates" comment="Menu notification that keyboard or dictionary updates available">تنزيل التحديثات</string>
|
||||
<!-- Context: Title -->
|
||||
<string name="title_version" comment="Title of Keyman for Android version">إصدار: %1$s</string>
|
||||
<!-- Context: Text label when old Chrome version installed -->
|
||||
<string name="text_require_chrome_57" comment="Banner text when old version of Chrome is installed">يتطلب Keyman إصدار 57 من Chrome أو أعلى.</string>
|
||||
<!-- Context: Button label -->
|
||||
<string name="button_update_chrome" comment="Prompt to upgrade Chrome in the Play Store">تحديث Chrome</string>
|
||||
<!-- Context: Textfield prompt (… is the ellipsis character "...") -->
|
||||
<string name="textview_hint" comment="Prompt to start typing">ابدأ الكتابة هنا…</string>
|
||||
<!-- Context: Splash screen (version/copyright info currently disabled -->
|
||||
<!-- Context: Splash screen (version/copyright info currently disabled -->
|
||||
<!-- Context: Text Size dialog -->
|
||||
<string name="text_size" comment="Current text size (number)">حجم النص: %1$d</string>
|
||||
<!-- Context: Text Size dialog -->
|
||||
<string name="ic_text_size_up" comment="Make text bigger">تكبير حجم النص</string>
|
||||
<!-- Context: Text Size dialog -->
|
||||
<string name="ic_text_size_down" comment="Make text smaller">تصغير حجم النص</string>
|
||||
<!-- Context: Text Size dialog -->
|
||||
<string name="ic_text_size_slider" comment="Adjust text size">Text size slider</string>
|
||||
<!-- Context: Clear Text dialog -->
|
||||
<string name="all_text_will_be_cleared" comment="Erase all the text">\nسيتم محو كل النص\n</string>
|
||||
<!-- Context: Get Started menu -->
|
||||
<string name="get_started" comment="Menu for getting started">إبدأ الآن</string>
|
||||
<!-- Context: Get Started menu -->
|
||||
<string name="add_a_keyboard" comment="Menu item to add a keyboard">اضف لوحة مفاتيح بلغتك</string>
|
||||
<!-- Context: Get Started menu -->
|
||||
<string name="enable_system_keyboard" comment="Menu item to enable Keyman as system keyboard">تمكين Keyman لوحة المفاتيح الأساسية للنظام</string>
|
||||
<!-- Context: Get Started menu -->
|
||||
<string name="set_keyman_as_default" comment="Menu item to set Keyman as default system keyboard">إعداد Keyman لوحة المفاتيح الإفتراضية</string>
|
||||
<!-- Context: Get Started menu -->
|
||||
<string name="more_info" comment="Open the Keyman for Android help page">المزيد من المعلومات</string>
|
||||
<!-- Context: Get Started menu -->
|
||||
<string name="show_get_started" comment="Show the "Get Started" menu on startup">أظهر \"%1$s\" عند البدء</string>
|
||||
<!-- Context: Android Storage Permission -->
|
||||
<string name="request_storage_permission" comment="Request storage permission to access keyboard packages">لتثبيت حزم لوحات المفاتيح، امنح Keyman الإذن لقراءة التخزين الخارجي.</string>
|
||||
<!-- Context: Android Storage Permission -->
|
||||
<string name="storage_permission_denied" comment="Keyboard package installation may fail since Android storage permission not granted">تم رفض أذونات التخزين. قد يفشل تثبيت حزمة لوحة المفاتيح</string>
|
||||
<string name="storage_permission_denied2" comment="Keyboard package installation failed. Recommend install from local file"> Storage permission request failed. Try Keyman Settings - Install from local file</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="keyman_settings" comment="Settings menu title">الإعدادات</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<plurals name="installed_languages">
|
||||
<item quantity="zero">اللغات المثبتة (%1$d)</item>
|
||||
<item quantity="one">اللغات المثبتة (%1$d)</item>
|
||||
<item quantity="two">اللغات المثبتة (%1$d)</item>
|
||||
<item quantity="few">اللغات المثبتة (%1$d)</item>
|
||||
<item quantity="many">اللغات المثبتة (%1$d)</item>
|
||||
<item quantity="other">اللغات المثبتة (%1$d)</item>
|
||||
</plurals>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="install_keyboard_or_dictionary" comment="Menu item to install keyboard or dictionary">تثبيت لوحة المفاتيح او القاموس</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="change_display_language" comment="Menu action to change interface language">لغة العرض</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="adjust_keyboard_height" comment="Menu action to adjust keyboard height">تعديل ارتفاع لوحة المفاتيح</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="spacebar_caption" comment="Menu action to set the spacebar caption">نص مفتاح المسافة</string>
|
||||
<!-- Context: Keyman Settings menu / Spacebar caption -->
|
||||
<string name="spacebar_caption_keyboard" comment="Spacebar caption option - keyboard">لوحة المفاتيح</string>
|
||||
<!-- Context: Keyman Settings menu / Spacebar caption -->
|
||||
<string name="spacebar_caption_language" comment="Spacebar caption option - language">اللغة</string>
|
||||
<!-- Context: Keyman Settings menu / Spacebar caption -->
|
||||
<string name="spacebar_caption_language_keyboard" comment="Spacebar caption option - language + keyboard">اللغة + لوحة المفاتيح</string>
|
||||
<!-- Context: Keyman Settings menu / Spacebar caption -->
|
||||
<string name="spacebar_caption_blank" comment="Spacebar caption option - blank">فارغ</string>
|
||||
<!-- Context: Keyman Settings menu / Spacebar caption hint -->
|
||||
<string name="spacebar_caption_hint_keyboard" comment="Spacebar caption hint - keyboard">أظهر اسم لوحة المفاتيح على مفتاح المسافة</string>
|
||||
<!-- Context: Keyman Settings menu / Spacebar caption hint -->
|
||||
<string name="spacebar_caption_hint_language" comment="Spacebar caption hint - language">أظهر اسم اللغة على المسافة</string>
|
||||
<!-- Context: Keyman Settings menu / Spacebar caption hint -->
|
||||
<string name="spacebar_caption_hint_language_keyboard" comment="Spacebar caption hint - language + keyboard">اللغة ولوحة المفاتيح على المسافة</string>
|
||||
<!-- Context: Keyman Settings menu / Spacebar caption hint -->
|
||||
<string name="spacebar_caption_hint_blank" comment="Spacebar caption hint - blank">لا تظهر نص على المسافة</string>
|
||||
<!-- Context: Keyman Settings menu / Haptic feedback -->
|
||||
<string name="haptic_feedback" comment="haptic feedback on key press">الاهتزاز عند الكتابة</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_banner" comment="text suggestions banner">إظهار اللافتة دائماً</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_banner_on" comment="Description when toggle is on">سيتم تنفيذها</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_banner_off" comment="Description when toggle is off">عند إيقاف التشغيل، يظهر فقط عند تفعيل النص التنبؤي</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_send_crash_report" comment="permission for sending crash reports">السماح بإرسال تقارير توقف التشغيل المفاجئ عبر الشبك</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_send_crash_report_on" comment="Description when toggle is on">عند التشغيل، سيتم إرسال تقارير التوقف المفاجئ</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_send_crash_report_off" comment="Description when toggle is off">عند إيقاف التشغيل، لن يتم إرسال التقارير</string>
|
||||
<!-- Context: Keyman Settings "Install Keyboard or Dictionary" menu -->
|
||||
<string name="install_from_keyman_dot_com" comment="Install package from Keyman server">تثبيت من keyman.com</string>
|
||||
<!-- Context: Keyman Settings "Install Keyboard or Dictionary" menu -->
|
||||
<string name="install_from_local_file" comment="Install package file from local device">التثبيت من ملف محلي</string>
|
||||
<!-- Context: Keyman Settings "Install Keyboard or Dictionary" menu -->
|
||||
<string name="install_from_other_device" comment="Scan QR Code">التثبيت من جهاز آخر</string>
|
||||
<!-- Context: Keyman Settings "Install Keyboard or Dictionary" menu -->
|
||||
<string name="add_languages_to_installed_keyboard" comment="Install additional languages from a keyboard package">اضف لغات للوحة المفاتيح المثبتة</string>
|
||||
<!-- Context: Keyman Settings "Install Keyboard or Dictionary" menu -->
|
||||
<string name="add_language_subtext" comment="Additional information for adding another language">(من حزمة لوحة المفاتيح)</string>
|
||||
<!-- Context: Select Package and Select Languages menu -->
|
||||
<string name="title_select_keyboard_package_list" comment="Select a keyboard package to add languages">اختر حزمة لوحة مفاتيح</string>
|
||||
<!-- Context: Select Package and Select Languages menu -->
|
||||
<string name="title_select_languages_for_package" comment="Select language names from the keyboard package">اختر لغة %1$s</string>
|
||||
<!-- Context: Select Package and Select Languages menu -->
|
||||
<string name="added_language_to_keyboard" comment="Notification that a language name is added to a keyboard">تمت اضافة %1$s إلى %2$s</string>
|
||||
<!-- Context: Select Package and Select Languages menu -->
|
||||
<string name="all_languages_installed" comment="Text when all languages from a keyboard package have been installed">كل اللغات مثبتة مسبقا</string>
|
||||
<!-- Context: Adjust Keyboard Height menu -->
|
||||
<string name="drag_the_keyboard" comment="First line of adjusting keyboard height">اسحب لوحة المفاتيح لتغيير مدى ارتفاعها</string>
|
||||
<!-- Context: Adjust Keyboard Height menu -->
|
||||
<string name="rotate_the_device" comment="Second line of adjusting keyboard height">دور الجهاز لضبط الوضع العمودي والوضع الأفقي</string>
|
||||
<!-- Context: Adjust Keyboard Height menu -->
|
||||
<string name="reset_to_defaults" comment="Button to reset to default heights">إعادة ضبط الافتراضيات</string>
|
||||
<!-- Context: Keyman Web Browser -->
|
||||
<string name="hint_text" comment="Prompt to type in the browser search bar">ابحث أو اكتب عنوان URL</string>
|
||||
<!-- Context: Keyman Web Browser -->
|
||||
<string name="title_bookmarks" comment="Title for bookmarks">علامات مرجعية</string>
|
||||
<!-- Context: Keyman Web Browser -->
|
||||
<string name="no_bookmarks" comment="Text when bookmark list is empty">لا تتوفر أي علامات مرجعية</string>
|
||||
<!-- Context: Keyman Web Browser -->
|
||||
<string name="add_bookmark" comment="Confirmation to add bookmark">أضف علامة مرجعية</string>
|
||||
<!-- Context: Keyman Web Browser -->
|
||||
<string name="hint_title" comment="Page title for saved bookmark">العنوان</string>
|
||||
<!-- Context: Keyman Web Browser -->
|
||||
<string name="hint_url" comment="Page URL for saved bookmark">عنوان Url</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="title_package_failed_to_install" comment="Error dialog title when package failed to install">فشل في تنزيل حزمة %1$s</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="downloading_keyboard_package" comment="Confirmation to download keyboard package filename">يتم الآن تحميل لوحة مفاتيح حزمة \n%1$s…</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="failed_to_extract" comment="Notification that keyboard package failed to unzip">فشل في الإستخراج</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="install_keyboard_package" comment="Title to install keyboard package">تثبيت لوحة المفاتيح</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="install_predictive_text_package" comment="Title to install dictionary package">تثبيت القاموس</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="not_valid_package_file" comment="Notification when invalid package file cannot be installed">ليس %1$s ملف حزمة صالحة لبرنامج Keyman.\n%2$s\"</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="no_new_touch_keyboards_to_install" comment="Notification when no touch-optimized keyboards can be installed">ليس في حزمة لوحة المفاتيح لوحات مفاتيح مكيّفة لشاشات اللمس</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="no_new_predictive_text_to_install" comment="Notification when no dictionaries can be installed">لا يوجد نصوص تنبؤية للتنزيل</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="no_targets_to_install" comment="Notification when a package doesn't contain keyboards or dictionaries">لا لوحات مفاتيح و نصوص تنبؤية للتنزيل</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="no_associated_languages" comment="Notification when a keyboard package doesn't contain languages">ليس لحزمة لوحة المفاتيح لغات ذات صلة لتثبيتها</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="invalid_metadata" comment="kmp.json metadata file is invalid or missing in package">غير صالح\ناقص البيانات الوصفية في الحزمة</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="minimum_keyboard_version_not_supported" comment="Notification when keyboard has functionality not supported by current Keyman">تتطلب لوحة المفاتيح إصدارًا أحدث من Keyman</string>
|
||||
<!-- Context: anywhere -->
|
||||
<string name="unable_to_open_browser" comment="Notification when a browser activity cannot be launched">غير قادر على تشغيل متصفح الويب</string>
|
||||
</resources>
|
||||
|
|
@ -30,9 +30,9 @@
|
|||
<!-- Context: Text Size dialog -->
|
||||
<string name="text_size" comment="Current text size (number)">Μέγεθος κειμένου: %1$d</string>
|
||||
<!-- Context: Text Size dialog -->
|
||||
<string name="ic_text_size_up" comment="Make text bigger">Μεγαλῶστε τὸ κείμενο</string>
|
||||
<string name="ic_text_size_up" comment="Make text bigger">Μεγαλῶστε τὴν γραμματοσειρά</string>
|
||||
<!-- Context: Text Size dialog -->
|
||||
<string name="ic_text_size_down" comment="Make text smaller">Μεγαλῶστε τὸ κείμενο</string>
|
||||
<string name="ic_text_size_down" comment="Make text smaller">Μικρύνετε τὴν γραμματοσειρά</string>
|
||||
<!-- Context: Text Size dialog -->
|
||||
<string name="ic_text_size_slider" comment="Adjust text size">Ρύθμιση μεγέθους κειμένου</string>
|
||||
<!-- Context: Clear Text dialog -->
|
||||
|
|
@ -68,7 +68,7 @@
|
|||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="adjust_keyboard_height" comment="Menu action to adjust keyboard height">Μεταβολὴ ὕψους πληκτρολογίου</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="spacebar_caption" comment="Menu action to set the spacebar caption">Spacebar caption</string>
|
||||
<string name="spacebar_caption" comment="Menu action to set the spacebar caption">Λεζάντα πλήκτρου διαστήματος</string>
|
||||
<!-- Context: Keyman Settings menu / Spacebar caption -->
|
||||
<string name="spacebar_caption_keyboard" comment="Spacebar caption option - keyboard">Πληκτρολόγιο</string>
|
||||
<!-- Context: Keyman Settings menu / Spacebar caption -->
|
||||
|
|
@ -80,9 +80,9 @@
|
|||
<!-- Context: Keyman Settings menu / Spacebar caption hint -->
|
||||
<string name="spacebar_caption_hint_keyboard" comment="Spacebar caption hint - keyboard">\'Ονομα πληκτρολογίου στὸ πλῆκτρο διαστήματος</string>
|
||||
<!-- Context: Keyman Settings menu / Spacebar caption hint -->
|
||||
<string name="spacebar_caption_hint_language" comment="Spacebar caption hint - language">\'Ονομα γλώσσας στὸ πλῆκτρο διαστήματος</string>
|
||||
<string name="spacebar_caption_hint_language" comment="Spacebar caption hint - language">Ὄνομα γλώσσας στὸ πλῆκτρο διαστήματος</string>
|
||||
<!-- Context: Keyman Settings menu / Spacebar caption hint -->
|
||||
<string name="spacebar_caption_hint_language_keyboard" comment="Spacebar caption hint - language + keyboard">\'Ονομα πληκτρολογίου καὶ γλώσσας στὸ πλῆκτρο διαστήματος</string>
|
||||
<string name="spacebar_caption_hint_language_keyboard" comment="Spacebar caption hint - language + keyboard">Ὄνομα πληκτρολογίου καὶ γλώσσας στὸ πλῆκτρο διαστήματος</string>
|
||||
<!-- Context: Keyman Settings menu / Spacebar caption hint -->
|
||||
<string name="spacebar_caption_hint_blank" comment="Spacebar caption hint - blank">Καμμία λεζάντα στὸ πλῆκτρο διαστήματος</string>
|
||||
<!-- Context: Keyman Settings menu / Haptic feedback -->
|
||||
|
|
@ -92,13 +92,13 @@
|
|||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_banner_on" comment="Description when toggle is on">Πρὸς ὑλοποίησιν</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_banner_off" comment="Description when toggle is off">Ὅταν εἶναι off, ἐμφανίζεται μόνο ὅταν ἔχει ἐνεργοποιηθεῖ τὸ προγνωστικὸ κείμενο</string>
|
||||
<string name="show_banner_off" comment="Description when toggle is off">OFF: ἐμφάνιση μόνον ὅταν ἔχει ἐνεργοποιηθεῖ ἡ πρόβλεψη κειμένου</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_send_crash_report" comment="permission for sending crash reports">Ἐπιτρέψτε τὴν ἀποστολὴ ἀναφορῶν κατάρρευσης μέσῳ δικτύου</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_send_crash_report_on" comment="Description when toggle is on">Ὅταν εἶναι ΟΝ, θὰ ἀποστέλλονται ἀναφορὲς κατάρρευσης</string>
|
||||
<string name="show_send_crash_report_on" comment="Description when toggle is on">ON: Ἀποστολὴ ἀναφορῶν κατάρρευσης</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_send_crash_report_off" comment="Description when toggle is off">Ὅταν εἶναι off, δὲν θὰ ἀποστέλλονται ἀναφορὲς κατάρρευσης</string>
|
||||
<string name="show_send_crash_report_off" comment="Description when toggle is off">OFF: Δὲν ἀποστέλλονται ἀναφορὲς κατάρρευσης</string>
|
||||
<!-- Context: Keyman Settings "Install Keyboard or Dictionary" menu -->
|
||||
<string name="install_from_keyman_dot_com" comment="Install package from Keyman server">Ἐγκατάσταση ἀπὸ τὸ keyman.com</string>
|
||||
<!-- Context: Keyman Settings "Install Keyboard or Dictionary" menu -->
|
||||
|
|
@ -118,9 +118,9 @@
|
|||
<!-- Context: Select Package and Select Languages menu -->
|
||||
<string name="all_languages_installed" comment="Text when all languages from a keyboard package have been installed">Ὅλες οἱ γλῶσσες ἔχουν ἤδη ἐγκατασταθεῖ</string>
|
||||
<!-- Context: Adjust Keyboard Height menu -->
|
||||
<string name="drag_the_keyboard" comment="First line of adjusting keyboard height">Σύρετε τὸ πληκτρολόγιο γιὰ νὰ ἀλλάξετε τὸ ὕψος</string>
|
||||
<string name="drag_the_keyboard" comment="First line of adjusting keyboard height">Σύρετε τὸ πληκτρολόγιο γιὰ νὰ ἀλλάξετε τὸ ὕψος του</string>
|
||||
<!-- Context: Adjust Keyboard Height menu -->
|
||||
<string name="rotate_the_device" comment="Second line of adjusting keyboard height">Περιστρέψτε τὴν συσκευὴ γιὰ λειτουργία πορτραίτου καὶ τοπίου</string>
|
||||
<string name="rotate_the_device" comment="Second line of adjusting keyboard height">Περιστρέψτε τὴν συσκευὴ γιὰ προσαρμογὴ τοῦ ὕψους σὲ λειτουργία πορτραίτου καὶ τοπίου</string>
|
||||
<!-- Context: Adjust Keyboard Height menu -->
|
||||
<string name="reset_to_defaults" comment="Button to reset to default heights">Ἐπαναφέρετε τὶς προεπιλεγμένες ρυθμίσεις</string>
|
||||
<!-- Context: Keyman Web Browser -->
|
||||
|
|
@ -150,9 +150,9 @@
|
|||
<!-- Context: KMP Package strings -->
|
||||
<string name="no_new_touch_keyboards_to_install" comment="Notification when no touch-optimized keyboards can be installed">Τὸ πακέτο πληκτρολογίου δὲν ἔχει βελτιστοποιημένα πληκτρολόγια ἀφῆς πρὸς ἐγκατάστασιν</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="no_new_predictive_text_to_install" comment="Notification when no dictionaries can be installed">Δὲν ὑπάρχει νέο προγνωστικό κείμενο πρὸς ἐγκατάστασιν</string>
|
||||
<string name="no_new_predictive_text_to_install" comment="Notification when no dictionaries can be installed">Δὲν ὑπάρχουν προγνώσεις κειμένων πρὸς ἐγκατάστασιν</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="no_targets_to_install" comment="Notification when a package doesn't contain keyboards or dictionaries">Δὲν ὑπάρχουν πληκτρολόγια ἢ προγνωστικό κείμενο πρὸς ἐγκατάστασιν</string>
|
||||
<string name="no_targets_to_install" comment="Notification when a package doesn't contain keyboards or dictionaries">Δὲν ὑπάρχουν πληκτρολόγια ἢ προγνώσεις κειμένων πρὸς ἐγκατάστασιν</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="no_associated_languages" comment="Notification when a keyboard package doesn't contain languages">Τὸ πακέτο πληκτρολογίου δὲν ἔχει σχετικὲς μὲ αὐτὸ γλῶσσες πρὸς ἐγκατάστασιν</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
|
|
|
|||
|
|
@ -23,4 +23,5 @@
|
|||
<dimen name="preference_icon">32dp</dimen>
|
||||
<dimen name="update_indicator_size">16dp</dimen>
|
||||
<dimen name="update_indicator_bg_margin">0dp</dimen>
|
||||
<dimen name="longpress_delay_label_textsize">24sp</dimen>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -113,6 +113,9 @@
|
|||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="adjust_keyboard_height" comment="Menu action to adjust keyboard height">Adjust keyboard height</string>
|
||||
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="adjust_longpress_delay" comment="Menu action to adjust longpress delay duration">Adjust longpress delay</string>
|
||||
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="spacebar_caption" comment="Menu action to set the spacebar caption">Spacebar caption</string>
|
||||
|
||||
|
|
@ -198,6 +201,17 @@
|
|||
<!-- Context: Adjust Keyboard Height menu -->
|
||||
<string name="reset_to_defaults" comment="Button to reset to default heights">Reset to Defaults</string>
|
||||
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="longpress_delay_time" comment="Current longpress delay time (seconds)">Delay Time: %1$.1f seconds</string>
|
||||
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_delay_time_up" comment="Make longpress delay longer">Delay time longer</string>
|
||||
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_delay_time_down" comment="Make longpress delay shorter">Delay time shorter</string>
|
||||
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_longpress_delay_slider" comment="Adjust longpress delay time">Longpress delay time slider</string>
|
||||
|
||||
<!-- Context: Keyman Web Browser -->
|
||||
<string name="hint_text" comment="Prompt to type in the browser search bar">Search or type URL</string>
|
||||
|
|
|
|||
|
|
@ -112,6 +112,17 @@ function notifyHost(event, params) {
|
|||
}, 10);
|
||||
}
|
||||
|
||||
// Update the KeymanWeb longpress delay
|
||||
// delay is in milliseconds
|
||||
function setLongpressDelay(delay) {
|
||||
if (keyman.osk) {
|
||||
keyman.osk.gestureParams.longpress.waitLength = delay;
|
||||
console.debug('setLongpressDelay('+delay+')');
|
||||
} else {
|
||||
window.console.log('setLongpressDelay error: keyman.osk undefined');
|
||||
}
|
||||
}
|
||||
|
||||
// Update the KMW banner height
|
||||
// h is in dpi (different from iOS)
|
||||
function setBannerHeight(h) {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ public class DisplayLanguages {
|
|||
DisplayLanguageType[] languages = {
|
||||
new DisplayLanguageType(unspecifiedLocale, context.getString(R.string.default_locale)),
|
||||
new DisplayLanguageType("am-ET", "አማርኛ (Amharic)"),
|
||||
new DisplayLanguageType("ar-SA", "(Arabic) العربية"),
|
||||
new DisplayLanguageType("az-AZ", "Azərbaycanca (Azəricə)"),
|
||||
new DisplayLanguageType("bwr-NG", "Bura-Pabir"),
|
||||
new DisplayLanguageType("cs-CZ", "čeština (Czech)"),
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import android.webkit.JavascriptInterface;
|
|||
|
||||
import static android.content.Context.VIBRATOR_SERVICE;
|
||||
|
||||
import com.keyman.engine.KMManager.EnterModeType;
|
||||
import com.keyman.engine.KMManager.KeyboardType;
|
||||
import com.keyman.engine.data.Keyboard;
|
||||
import com.keyman.engine.util.CharSequenceUtil;
|
||||
|
|
@ -153,7 +154,53 @@ public class KMKeyboardJSHandler {
|
|||
}
|
||||
|
||||
if (s.length() > 0 && s.charAt(0) == '\n') {
|
||||
keyDownUp(KeyEvent.KEYCODE_ENTER, 0);
|
||||
if (k.keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
|
||||
// Special handling of ENTER key
|
||||
switch (KMManager.enterMode) {
|
||||
// Go action
|
||||
case GO :
|
||||
ic.performEditorAction(EditorInfo.IME_ACTION_GO);
|
||||
break;
|
||||
|
||||
// Search action
|
||||
case SEARCH :
|
||||
ic.performEditorAction(EditorInfo.IME_ACTION_SEARCH);
|
||||
break;
|
||||
|
||||
// Send action
|
||||
case SEND :
|
||||
ic.performEditorAction(EditorInfo.IME_ACTION_SEND);
|
||||
break;
|
||||
|
||||
// Next action
|
||||
case NEXT :
|
||||
ic.performEditorAction(EditorInfo.IME_ACTION_NEXT);
|
||||
break;
|
||||
|
||||
// Done action
|
||||
case DONE :
|
||||
ic.performEditorAction(EditorInfo.IME_ACTION_DONE);
|
||||
break;
|
||||
|
||||
// Previous action
|
||||
case PREVIOUS :
|
||||
ic.performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);
|
||||
break;
|
||||
|
||||
// Messaging apps
|
||||
case NEWLINE :
|
||||
// Send newline and advance cursor
|
||||
ic.commitText("\n", 1);
|
||||
break;
|
||||
|
||||
// Default ENTER action
|
||||
default:
|
||||
keyDownUp(KeyEvent.KEYCODE_ENTER, 0);
|
||||
}
|
||||
} else {
|
||||
// In-app keyboard uses default ENTER action
|
||||
keyDownUp(KeyEvent.KEYCODE_ENTER, 0);
|
||||
}
|
||||
ic.endBatchEdit();
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import android.content.SharedPreferences;
|
|||
import android.content.pm.PackageManager;
|
||||
import android.content.res.AssetManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Typeface;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
|
|
@ -180,6 +179,19 @@ public final class KMManager {
|
|||
}
|
||||
}
|
||||
|
||||
// Enum for how the System Keyboard ENTER key is handled for the EditorInfo action
|
||||
// Reference: https://developer.android.com/reference/android/view/inputmethod/EditorInfo#summary
|
||||
public enum EnterModeType {
|
||||
GO, // Go action
|
||||
SEARCH, // Search action
|
||||
SEND, // Send action
|
||||
NEXT, // Next action
|
||||
DONE, // Done action
|
||||
PREVIOUS, // Previous action
|
||||
NEWLINE, // Send newline character
|
||||
DEFAULT, // Default ENTER action
|
||||
}
|
||||
|
||||
protected static InputMethodService IMService;
|
||||
|
||||
private static boolean debugMode = false;
|
||||
|
|
@ -218,6 +230,9 @@ public final class KMManager {
|
|||
// regardless what the Settings preference is.
|
||||
private static boolean mayPredictOverride = false;
|
||||
|
||||
// Determine how system keyboard handles ENTER key
|
||||
public static EnterModeType enterMode = EnterModeType.DEFAULT;
|
||||
|
||||
// Boolean for whether a keyboard can send embedded KMW crash reports to Sentry
|
||||
// When maySendCrashReport is false, KMW will still attempt to send crash reports, but it
|
||||
// will be blocked.
|
||||
|
|
@ -248,6 +263,8 @@ public final class KMManager {
|
|||
public static final String KMKey_KeyboardHeightPortrait = "keyboardHeightPortrait";
|
||||
public static final String KMKey_KeyboardHeightLandscape = "keyboardHeightLandscape";
|
||||
|
||||
public static final String KMKey_LongpressDelay = "longpressDelay";
|
||||
|
||||
public static final String KMKey_CustomHelpLink = "CustomHelpLink";
|
||||
public static final String KMKey_KMPLink = "kmp";
|
||||
public static final String KMKey_UserKeyboardIndex = "UserKeyboardIndex";
|
||||
|
|
@ -293,6 +310,9 @@ public final class KMManager {
|
|||
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
|
||||
public static final int KMDefault_LongpressDelay = 500;
|
||||
|
||||
// Keyman files
|
||||
protected static final String KMFilename_KeyboardHtml = "keyboard.html";
|
||||
protected static final String KMFilename_JSEngine = "keymanweb-webview.js";
|
||||
|
|
@ -1251,6 +1271,61 @@ public final class KMManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets enterMode which specifies how the System keyboard ENTER key is handled
|
||||
*
|
||||
* @param imeOptions EditorInfo.imeOptions
|
||||
* @param inputType InputType
|
||||
*/
|
||||
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;
|
||||
|
||||
switch (imeActions) {
|
||||
case EditorInfo.IME_ACTION_GO:
|
||||
value = EnterModeType.GO;
|
||||
break;
|
||||
|
||||
case EditorInfo.IME_ACTION_SEARCH:
|
||||
value = EnterModeType.SEARCH;
|
||||
break;
|
||||
|
||||
case EditorInfo.IME_ACTION_SEND:
|
||||
value = EnterModeType.SEND;
|
||||
break;
|
||||
|
||||
case EditorInfo.IME_ACTION_NEXT:
|
||||
value = EnterModeType.NEXT;
|
||||
break;
|
||||
|
||||
case EditorInfo.IME_ACTION_DONE:
|
||||
value = EnterModeType.DONE;
|
||||
break;
|
||||
|
||||
case EditorInfo.IME_ACTION_PREVIOUS:
|
||||
value = EnterModeType.PREVIOUS;
|
||||
break;
|
||||
|
||||
default:
|
||||
value = EnterModeType.DEFAULT;
|
||||
}
|
||||
|
||||
enterMode = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of enterMode
|
||||
* @return EnterModeType
|
||||
*/
|
||||
public static EnterModeType getEnterMode() {
|
||||
return enterMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets mayPredictOverride true if the InputType field is a hidden password text field
|
||||
* (either TYPE_TEXT_VARIATION_PASSWORD or TYPE_TEXT_VARIATION_WEB_PASSWORD
|
||||
|
|
@ -2010,6 +2085,45 @@ public final class KMManager {
|
|||
return Configuration.ORIENTATION_UNDEFINED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the longpress delay (in milliseconds) from stored preference. Defaults to 500ms
|
||||
* @return int - longpress delay in milliseconds
|
||||
*/
|
||||
public static int getLongpressDelay() {
|
||||
SharedPreferences prefs = appContext.getSharedPreferences(
|
||||
appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
|
||||
|
||||
return prefs.getInt(KMKey_LongpressDelay, KMDefault_LongpressDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the longpress delay (in milliseconds) as a stored preference.
|
||||
* @param longpressDelay - int longpress delay in milliseconds
|
||||
*/
|
||||
public static void setLongpressDelay(int longpressDelay) {
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends options to the KeymanWeb keyboard.
|
||||
* 1. number of milliseconds to trigger a longpress gesture.
|
||||
* This method requires a keyboard to be loaded for the value to take effect.
|
||||
*/
|
||||
public static void sendOptionsToKeyboard() {
|
||||
int longpressDelay = getLongpressDelay();
|
||||
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP)) {
|
||||
InAppKeyboard.loadJavascript(KMString.format("setLongpressDelay(%d)", longpressDelay));
|
||||
}
|
||||
|
||||
if (SystemKeyboard != null) {
|
||||
SystemKeyboard.loadJavascript(KMString.format("setLongpressDelay(%d)", longpressDelay));
|
||||
}
|
||||
}
|
||||
|
||||
public static int getBannerHeight(Context context) {
|
||||
int bannerHeight = 0;
|
||||
if (InAppKeyboard != null && InAppKeyboard.getBanner() != BannerType.BLANK) {
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ public final class KeyboardInfoActivity extends BaseActivity {
|
|||
hashMap = new HashMap<String, String>();
|
||||
final String customHelpLink = kbd.getHelpLink();
|
||||
// Check if app declared FileProvider
|
||||
String icon = String.valueOf(R.drawable.ic_arrow_forward);
|
||||
String icon = String.valueOf(R.drawable.ic_action_forward);
|
||||
// Don't show help link arrow if it's a local help file and File Provider unavailable,
|
||||
// or custom help doesn't exist
|
||||
if ( (customHelpLink != null && ! KMManager.isTestMode() &&
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ public final class ModelInfoActivity extends BaseActivity {
|
|||
final String customHelpLink = lm.getHelpLink();
|
||||
// Check if app declared FileProvider
|
||||
// Currently, model help only available if custom link exists
|
||||
icon = String.valueOf(R.drawable.ic_arrow_forward);
|
||||
icon = String.valueOf(R.drawable.ic_action_forward);
|
||||
// Don't show help link arrow if both custom help and File Provider don't exist
|
||||
// TODO: Update this when model help available on help.keyman.com
|
||||
if ( (!customHelpLink.equals("") && !FileProviderUtils.exists(context)) ||
|
||||
|
|
|
|||
|
|
@ -321,7 +321,7 @@ public final class ModelPickerActivity extends BaseActivity {
|
|||
// once it has its own backing Dataset instance.
|
||||
// Is this an installed model or not?
|
||||
|
||||
holder.imgDetails.setImageResource(R.drawable.ic_arrow_forward);
|
||||
holder.imgDetails.setImageResource(R.drawable.ic_action_forward);
|
||||
if (KeyboardPickerActivity.containsLexicalModel(context, modelKey)) {
|
||||
holder.imgInstalled.setImageResource(R.drawable.ic_check);
|
||||
} else {
|
||||
|
|
|
|||
BIN
android/KMEA/app/src/main/res/drawable-hdpi/ic_arrow_back.png
Normal file
|
After Width: | Height: | Size: 321 B |
BIN
android/KMEA/app/src/main/res/drawable-mdpi/ic_arrow_back.png
Normal file
|
After Width: | Height: | Size: 270 B |
BIN
android/KMEA/app/src/main/res/drawable-xhdpi/ic_arrow_back.png
Normal file
|
After Width: | Height: | Size: 330 B |
BIN
android/KMEA/app/src/main/res/drawable-xxhdpi/ic_arrow_back.png
Normal file
|
After Width: | Height: | Size: 352 B |
BIN
android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_arrow_back.png
Normal file
|
After Width: | Height: | Size: 413 B |
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:src="@drawable/ic_arrow_back"
|
||||
android:tint="@android:color/black"
|
||||
android:autoMirrored="true" />
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:src="@drawable/ic_arrow_forward"
|
||||
android:tint="@color/keyman_blue" />
|
||||
android:tint="@android:color/black"
|
||||
android:autoMirrored="true"/>
|
||||
|
|
@ -15,8 +15,8 @@
|
|||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:paddingBottom="10dp"
|
||||
android:paddingLeft="2dp"
|
||||
android:paddingRight="2dp"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="2dp"
|
||||
android:text="@string/help_bubble_text"
|
||||
android:textColor="#404040"
|
||||
android:textSize="12sp" />
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
android:src="@drawable/ic_arrow_forward"
|
||||
android:src="@drawable/ic_action_forward"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="12dp"
|
||||
|
|
|
|||
188
android/KMEA/app/src/main/res/values-ar-rSA/strings.xml
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Device type -->
|
||||
<!-- Application name (Keyman Engine for Android) -->
|
||||
<!-- Context: Title for list -->
|
||||
<plurals name="title_keyboards">
|
||||
<item quantity="zero">لوحة مفاتيح</item>
|
||||
<item quantity="one">لوحات مفاتيح</item>
|
||||
<item quantity="two">لوحتا مفاتيح</item>
|
||||
<item quantity="few">لوحات مفاتيح</item>
|
||||
<item quantity="many">لوحة مفاتيح</item>
|
||||
<item quantity="other">لوحة مفاتيح</item>
|
||||
</plurals>
|
||||
<!-- Context: Title for list -->
|
||||
<plurals name="title_other_input_methods">
|
||||
<item quantity="zero">طريقة إداخل أخرى</item>
|
||||
<item quantity="one">طريقة إدخال أخرى</item>
|
||||
<item quantity="two">طريقتا إدخال أخريتان</item>
|
||||
<item quantity="few">طرق إدخال</item>
|
||||
<item quantity="many">طريقة إدخال أخرى</item>
|
||||
<item quantity="other">طريقة إدخال أخرى</item>
|
||||
</plurals>
|
||||
<!-- Context: Title for list -->
|
||||
<string name="title_add_keyboard" comment="Add a new keyboard">إضافة لوحة مفاتيح جديدة</string>
|
||||
<!-- Context: Title for list -->
|
||||
<string name="title_languages_settings" comment="List of installed languages">اللغات المثبتة</string>
|
||||
<!-- Context: Title for list -->
|
||||
<string name="title_language_settings" comment="Keyman Settings for a language">إعدادات %1$s</string>
|
||||
<!-- Context: Button label -->
|
||||
<string name="label_add" comment="Button to add an item">إضافة</string>
|
||||
<!-- Context: Button label -->
|
||||
<string name="label_back" comment="Button to go back">عودة</string>
|
||||
<!-- Context: Button label -->
|
||||
<string name="label_cancel" comment="Button to cancel an action">إلغاء</string>
|
||||
<!-- Context: Button label -->
|
||||
<string name="label_close" comment="Close a dialog">إغلاق</string>
|
||||
<!-- Context: Button label. Removed in Keyman 15 -->
|
||||
<string name="label_close_keyman" comment="Close the Keyman application">إغلاق Keyman</string>
|
||||
<!-- Context: Button label -->
|
||||
<string name="label_forward" comment="Button to go forward">الأمام</string>
|
||||
<!-- Context: Button label -->
|
||||
<string name="label_next_input_method" comment="Switch to next input method (replaces label_close_keyman)">طريقة الإدخال التالية</string>
|
||||
<!-- Context: Button label -->
|
||||
<string name="label_download" comment="Button to confirm downloading an item">تحميل</string>
|
||||
<!-- Context: Button label -->
|
||||
<string name="label_install" comment="Confirmation to install a package">تثبيت</string>
|
||||
<!-- Context: Button label -->
|
||||
<string name="label_later" comment="Do an action at a later time">لاحقًا</string>
|
||||
<!-- Context: Button label -->
|
||||
<string name="label_next" comment="Go to the next screen">التالي</string>
|
||||
<!-- Context: Button label -->
|
||||
<string name="label_ok" comment="Button to acknowledge a dialog">موافق</string>
|
||||
<!-- Context: Button label -->
|
||||
<string name="label_update" comment="Dialog button to update a resource">تحديث</string>
|
||||
<!-- Context: No internet for updates -->
|
||||
<string name="no_internet_connection" comment="Internet connection missing">لا يوجد اتصال بالإنترنت</string>
|
||||
<!-- Context: Keyboard Updates -->
|
||||
<string name="cannot_connect" comment="Error message when network connection fails">لا يمكن الاتصال بخادم Keyman!</string>
|
||||
<!-- Context: Keyboard Updates -->
|
||||
<string name="confirm_delete_keyboard" comment="Confirmation to delete a keyboard">هل ترغب بإزالة لوحة المفاتيح هذه؟</string>
|
||||
<!-- Context: Keyboard Updates -->
|
||||
<string name="confirm_download_keyboard" comment="Confirmation to download a keyboard update">هل ترغب بتحميل آخر إصدار من لوحة المفاتيح هذه؟</string>
|
||||
<!-- Context: Keyboard Updates -->
|
||||
<string name="confirm_update" comment="Confirmation to update several resources">هل ترغب بتحديث لوحات المفاتيح والقواميس الآن؟</string>
|
||||
<!-- Context: Keyboard Updates -->
|
||||
<string name="keyboard_updates_channel">تحديثات الموارد</string>
|
||||
<!-- Context: Keyboard Updates -->
|
||||
<string name="keyboard_updates_available" comment="Notification when resource updates are available">تتوفر تحديثات موارد</string>
|
||||
<!-- Context: Keyboard Updates -->
|
||||
<string name="update_available" comment="Currently installed version of a resource with an available update">%1$s (تحديث متوفر)</string>
|
||||
<!-- Context: Keyboard Updates -->
|
||||
<string name="keyboard_update_message" comment="language name: keyboard name">تحديثات متوفرة للوحة مفاتيح %1$s: %2$s </string>
|
||||
<!-- Context: Keyboard Updates -->
|
||||
<string name="dictionary_update_message">تحديثات متوفرة لقاموس %1$s: %2$s </string>
|
||||
<!-- Context: Keyboard Info and Keyboard Settings -->
|
||||
<string name="keyboard_version" comment="Label for keyboard version">إصدار لوحة المفاتيح</string>
|
||||
<!-- Context: Keyboard Info and Keyboard Settings -->
|
||||
<string name="help_link" comment="Label for help link">رابط المساعدة</string>
|
||||
<!-- Context: Keyboard Info and Keyboard Settings -->
|
||||
<string name="uninstall_keyboard" comment="Label for uninstalling keyboard">إزالة تثبيت لوحة المفاتيح</string>
|
||||
<!-- Context: Keyboard Info and Keyboard Settings -->
|
||||
<string name="keyboard_picker_new_keyboard" comment="Mark a language name that's newly installed in keyboard list">[new] %1$s</string>
|
||||
<!-- Context: Keyboard Info and Keyboard Settings -->
|
||||
<string name="keyboard_qr_code" comment="QR Code description">امسح هذا الرمز لتحميل \nلوحة المفاتيح هذه على جهاز آخر</string>
|
||||
<!-- Context: Keyboard Help welcome.htm title -->
|
||||
<string name="welcome_package" comment="Title to welcome.htm page (name and version)">أهلًا بك في %1$s</string>
|
||||
<!-- Context: Keyboard app doesn't include FileProvider library needed to view help file -->
|
||||
<string name="fileprovider_undefined" comment="FileProvider library needed to view help file">مكتبة FileProvider مطلوبة لعرض ملفات المساعدة: %1$s</string>
|
||||
<!-- Context: Fatal keyboard error. Will load default keyboard -->
|
||||
<string name="fatal_keyboard_error" comment="Fatal keyboard error (keyboard ID::packageID for language). Will load default keyboard">خطأ كبير في لوحة مفاتيح مع %1$s:%2$s لأجل لغة %3$s. سيتم تحميل لوحة المفاتيح الأساسية.</string>
|
||||
<!-- Context: Fatal keyboard error. -->
|
||||
<string name="fatal_keyboard_error_short" comment="Error in keyboard (keyboard ID::packageID for language).">خطأ في لوحة مفاتيح %1$s:%2$s لأجل لغة %3$s.</string>
|
||||
<!-- Context: Query for associated dictionary -->
|
||||
<string name="query_associated_model" comment="Check if there's an available dictionary to download">يجري التحقق من قاموس ذي صلة للتحميل</string>
|
||||
<string name="cannot_query_associated_model" comment="Cannot reach server to check if there's an available dictionary to download"> لا يمكن الاتصال بخادم Keyman للتحقق من تنزيل القاموس المرتبط</string>
|
||||
<!-- Context: Model Updates -->
|
||||
<string name="confirm_download_model" comment="Confirmation to download a dictionary update">هل ترغب بتحملي آخر إصدار من هذا القاموس؟</string>
|
||||
<!-- Context: No associated dictionary found -->
|
||||
<string name="no_associated_model" comment="Confirmation there's no available dictionary to download">لا يوجد قاموس لتحميله</string>
|
||||
<!-- Context: Model Updates -->
|
||||
<string name="catalog_unavailable" comment="Notification that the cloud resource catalog can't be updated">كتالوغ المصدر غير متوفر</string>
|
||||
<!-- Context: Background download messages-->
|
||||
<string name="catalog_download_start_in_background" comment="Notification that the resource catalog update will begin">بدء تحديث الكتالوغ في الخلفية</string>
|
||||
<!-- Context: Background download messages-->
|
||||
<string name="catalog_download_is_running_in_background" comment="Notification that the resource catalog update is still downloading">الكتالوغ لا يزال يتم تحميله، يرجى المحاولة مجددًا بعد قليل!</string>
|
||||
<!-- Context: Background download messages-->
|
||||
<string name="progress_message_checking_resource_is_running" comment="Progress notification that the check for a resource is still running ">البحث عن مصدر</string>
|
||||
<!-- Context: Background download messages-->
|
||||
<string name="keyboard_download_start_in_background" comment="Notification that a keyboard download has started">بدء تحميل لوحة المفاتيح في الخلفية</string>
|
||||
<!-- Context: Background download messages-->
|
||||
<string name="keyboard_download_is_running_in_background" comment="Notification that a keyboard download is still running">يجري بالفعل تحميل لوحة المفاتيح المختارة، يرجى المحاولة مجددًا بعد قليل!</string>
|
||||
<!-- Context: Background download messages-->
|
||||
<string name="keyboard_download_finished" comment="Notification that a keyboard download has finished">اكتمل تحميل لوحة المفاتيح!</string>
|
||||
<!-- Context: Background download messages-->
|
||||
<string name="dictionary_download_start_in_background" comment="Notification that a dictionary download has started">بدأ تحميل القاموس في الخلفية</string>
|
||||
<!-- Context: Background download messages-->
|
||||
<string name="dictionary_download_is_running_in_background" comment="Notification that a dictionary download is still running">يجري بالفعل تحميل القاموس، يرجى المحاولة مجددًا بعد قليل!</string>
|
||||
<!-- Context: Background download messages. Removed in Keyman 17-->
|
||||
<string name="dictionary_download_finished" comment="Notification that a dictionary download has finished">اكتمل تحميل القاموس.</string>
|
||||
<!-- Context: Background download messages -->
|
||||
<string name="download_failed" comment="Notification that a download failed">فشل التحميل</string>
|
||||
<!-- Context: Background download messages -->
|
||||
<string name="failed_to_retrieve_file" comment="Failed to retrieve downloaded file">فشل استرجاع الملف المحمل</string>
|
||||
<!-- Context: General Updates -->
|
||||
<string name="update_check_unavailable" comment="Error message when a Keyman server can't be reached">فشل الوصول إلى الخادم!</string>
|
||||
<!-- Context: General Updates -->
|
||||
<string name="update_check_current" comment="Notification that all resources are up to date">"جميع الموارد محدّثة!"</string>
|
||||
<!-- Context: General Updates -->
|
||||
<string name="update_failed" comment="Notification that a resource update failed">فشل تحديث واحد أو أكثر من المصادر!</string>
|
||||
<!-- Context: General Updates. Removed in Keyman 17 -->
|
||||
<string name="update_success" comment="Notification that a resource update successfully updated">تم تحديث الموارد بنجاح!</string>
|
||||
<!-- Context: Model Info -->
|
||||
<string name="model_version" comment="Title for a dictionary version">إصدار القاموس</string>
|
||||
<!-- Context: Model Info -->
|
||||
<string name="uninstall_model" comment="Label to uninstall a dictionary">إزالة تثبيت القاموس</string>
|
||||
<!-- Context: Model Info -->
|
||||
<string name="confirm_delete_model" comment="Confirmation to delete a dictionary">هل ترغب بإزالة هذا القاموس؟</string>
|
||||
<!-- Context: Model Deleted -->
|
||||
<string name="model_deleted" comment="Notification when a dictionary is deleted">تم حذف القاموس</string>
|
||||
<!-- Context: Language Settings Keyboard install strings -->
|
||||
<string name="keyboard_install_toast" comment="Notification when a keyboard is installed">تم تثبيت لوحة مفاتيح %1$s</string>
|
||||
<!-- Context: Language Settings Keyboard uninstall strings -->
|
||||
<string name="keyboard_deleted_toast" comment="Notification when a keyboard is deleted">تم حذف لوحة المفاتيح</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<string name="enable_corrections" comment="Enable corrections from the suggestion banner">تفعيل التصحيحات</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<string name="enable_predictions" comment="Enable predictions from the suggestion banner">تفعيل التوقعات</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<string name="model_label">القاموس</string>
|
||||
<plurals name="model_count">
|
||||
<item quantity="zero">Dictionaries</item>
|
||||
<item quantity="one">Dictionary</item>
|
||||
<item quantity="two">Dictionaries</item>
|
||||
<item quantity="few">Dictionaries</item>
|
||||
<item quantity="many">Dictionaries</item>
|
||||
<item quantity="other">Dictionaries</item>
|
||||
</plurals>
|
||||
<!-- Context: Language Settings menu substring - Check for available dictionary -->
|
||||
<string name="check_available_model" comment="Check for available dictionary">البحث عن قاموس متوفر</string>
|
||||
<string name="check_model_online" comment="Check for dictionaries online">تحقق من وجود قواميس على الإنترنت</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<string name="model_info_header" comment="Dictionary name">قاموس: %1$s</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<string name="model_picker_header" comment="Dictionaries for a specified language">قواميس %1$s</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<string name="model_install_toast" comment="Notification when a dictionary is installed">تم تثبيت القاموس</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<plurals name="keyboard_count">
|
||||
<item quantity="zero">(%1$d اوحات)</item>
|
||||
<item quantity="one">(لوحة مفاتيح %1$d)</item>
|
||||
<item quantity="two">(لوحتا مفاتيح %1$d)</item>
|
||||
<item quantity="few">(%1$d لوحة مفاتيح)</item>
|
||||
<item quantity="many">(%1$d لوحة مفاتيح)</item>
|
||||
<item quantity="other">(%1$d لوحة مفاتيح)</item>
|
||||
</plurals>
|
||||
<!-- Context: "Change Display Language" menu -->
|
||||
<string name="default_locale" comment="Use the device's current locale">اللغة الأصلية</string>
|
||||
<!-- Context: Content descriptions -->
|
||||
<!-- Context: Content descriptions -->
|
||||
<!-- Context: Popup menu labels -->
|
||||
<string name="label_delete" comment="Confirmation to delete an item">حذف</string>
|
||||
<!-- Context: Shared preferences name -->
|
||||
<!-- Context: Other strings -->
|
||||
<string name="help_bubble_text">اضغط هنا لتغيير لوحة المفاتيح</string>
|
||||
<!-- Context: anywhere -->
|
||||
<string name="unable_to_open_browser" comment="Notification when a browser activity cannot be launched">غير قادر على تشغيل متصفح الويب</string>
|
||||
</resources>
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
<!-- Context: Button label -->
|
||||
<string name="label_close" comment="Close a dialog">Κλεῖστε</string>
|
||||
<!-- Context: Button label. Removed in Keyman 15 -->
|
||||
<string name="label_close_keyman" comment="Close the Keyman application">Κλεῖστε τὸ Κῆμαν</string>
|
||||
<string name="label_close_keyman" comment="Close the Keyman application">Κλεῖστε τὸ Keyman</string>
|
||||
<!-- Context: Button label -->
|
||||
<string name="label_forward" comment="Button to go forward">Προχωρῆστε</string>
|
||||
<!-- Context: Button label -->
|
||||
|
|
@ -53,11 +53,11 @@
|
|||
<!-- Context: Keyboard Updates -->
|
||||
<string name="confirm_download_keyboard" comment="Confirmation to download a keyboard update">Θὰ θέλατε νὰ κατεβάσετε τὴν τελευταία ἔκδοση αὐτοῦ τοῦ πληκτρολογίου;</string>
|
||||
<!-- Context: Keyboard Updates -->
|
||||
<string name="confirm_update" comment="Confirmation to update several resources">Θά θέλατε νὰ ἐνημερώσετε τώρα πληκτρολόγια καὶ λεξικά;</string>
|
||||
<string name="confirm_update" comment="Confirmation to update several resources">Θὰ θέλατε νὰ ἐνημερώσετε τώρα πληκτρολόγια καὶ λεξικά;</string>
|
||||
<!-- Context: Keyboard Updates -->
|
||||
<string name="keyboard_updates_channel">Ἐνημερώσεις Πόρων</string>
|
||||
<string name="keyboard_updates_channel">Ἐνημερώσεις</string>
|
||||
<!-- Context: Keyboard Updates -->
|
||||
<string name="keyboard_updates_available" comment="Notification when resource updates are available">Διαθέσιμες Ἐνημερώσεις Πόρων</string>
|
||||
<string name="keyboard_updates_available" comment="Notification when resource updates are available">Διαθέσιμες Ἐνημερώσεις</string>
|
||||
<!-- Context: Keyboard Updates -->
|
||||
<string name="update_available" comment="Currently installed version of a resource with an available update">%1$s (Διαθέσιμη Ἐνημέρωση)</string>
|
||||
<!-- Context: Keyboard Updates -->
|
||||
|
|
@ -67,13 +67,13 @@
|
|||
<!-- Context: Keyboard Info and Keyboard Settings -->
|
||||
<string name="keyboard_version" comment="Label for keyboard version">Ἔκδοση πληκτρολογίου</string>
|
||||
<!-- Context: Keyboard Info and Keyboard Settings -->
|
||||
<string name="help_link" comment="Label for help link">Σύνδεσμος βοηθείας</string>
|
||||
<string name="help_link" comment="Label for help link">Βοήθεια</string>
|
||||
<!-- Context: Keyboard Info and Keyboard Settings -->
|
||||
<string name="uninstall_keyboard" comment="Label for uninstalling keyboard">Ἀπεγκαταστῆστε πληκτρολόγιο</string>
|
||||
<!-- Context: Keyboard Info and Keyboard Settings -->
|
||||
<string name="keyboard_picker_new_keyboard" comment="Mark a language name that's newly installed in keyboard list">[νέο] %1$s</string>
|
||||
<!-- Context: Keyboard Info and Keyboard Settings -->
|
||||
<string name="keyboard_qr_code" comment="QR Code description">Σαρῶστε αὐτὸν τὸν κωδικό γιὰ νὰ φορτώσετε\nαὐτὸ τὸ πληκτρολόγιο σὲ ἄλλη συσκευή</string>
|
||||
<string name="keyboard_qr_code" comment="QR Code description">Σαρῶστε αὐτὸν τὸν κωδικὸ γιὰ νὰ φορτώσετε\nαὐτὸ τὸ πληκτρολόγιο σὲ ἄλλη συσκευή</string>
|
||||
<!-- Context: Keyboard Help welcome.htm title -->
|
||||
<string name="welcome_package" comment="Title to welcome.htm page (name and version)">Καλωσορίσατε στὸ %1$s</string>
|
||||
<!-- Context: Keyboard app doesn't include FileProvider library needed to view help file -->
|
||||
|
|
@ -81,22 +81,22 @@
|
|||
<!-- Context: Fatal keyboard error. Will load default keyboard -->
|
||||
<string name="fatal_keyboard_error" comment="Fatal keyboard error (keyboard ID::packageID for language). Will load default keyboard">Μοιραῖο σφάλμα πληκτρολογίου στὸ %1$s:%2$s γιὰ τὴν %3$s γλῶσσα. Φορτώνεται προεπιλεγμένο πληκτρολόγιο.</string>
|
||||
<!-- Context: Fatal keyboard error. -->
|
||||
<string name="fatal_keyboard_error_short" comment="Error in keyboard (keyboard ID::packageID for language)."> Error in keyboard %1$s:%2$s for %3$s language.</string>
|
||||
<string name="fatal_keyboard_error_short" comment="Error in keyboard (keyboard ID::packageID for language).">Σφάλμα στὸ πληκτρολόγιο %1$s:%2$s γιὰ τὴν γλῶσσα \"%3$s\".</string>
|
||||
<!-- Context: Query for associated dictionary -->
|
||||
<string name="query_associated_model" comment="Check if there's an available dictionary to download">Ἔλεγχος συσχετισμένου λεξικοῦ πρὸς λῆψιν</string>
|
||||
<string name="cannot_query_associated_model" comment="Cannot reach server to check if there's an available dictionary to download">Ἀδυναμία συνδέσεως μὲ τὸν διακομιστὴ Κῆμαν γιὰ τὸν ἔλεγχο συσχετισμένου λεξικοῦ πρὸς λῆψιν</string>
|
||||
<string name="query_associated_model" comment="Check if there's an available dictionary to download">Ἀναζήτηση σχετικοῦ λεξικοῦ πρὸς μεταφόρτωσιν</string>
|
||||
<string name="cannot_query_associated_model" comment="Cannot reach server to check if there's an available dictionary to download">Ἀδυναμία συνδέσεως μὲ τὸν διακομιστὴ Keyman γιὰ τὴν εὕρεση σχετικοῦ λεξικοῦ πρὸς μεταφόρτωσιν</string>
|
||||
<!-- Context: Model Updates -->
|
||||
<string name="confirm_download_model" comment="Confirmation to download a dictionary update">Θὰ θέλατε νὰ κατεβάσετε τὴν τελευταία ἔκδοση αὐτοῦ τοῦ λεξικοῦ;</string>
|
||||
<!-- Context: No associated dictionary found -->
|
||||
<string name="no_associated_model" comment="Confirmation there's no available dictionary to download">Δὲν ὑπάρχει λεξικὸ πρὸς λῆψιν</string>
|
||||
<string name="no_associated_model" comment="Confirmation there's no available dictionary to download">Δὲν ὑπάρχει λεξικὸ πρὸς μεταφόρτωσιν</string>
|
||||
<!-- Context: Model Updates -->
|
||||
<string name="catalog_unavailable" comment="Notification that the cloud resource catalog can't be updated">Μὴ διαθέσιμος κατάλογος πόρων</string>
|
||||
<!-- Context: Background download messages-->
|
||||
<string name="catalog_download_start_in_background" comment="Notification that the resource catalog update will begin">Ἔχει ξεκινήσει ἐνημέρωση καταλόγου στὸ παρασκήνιο</string>
|
||||
<!-- Context: Background download messages-->
|
||||
<string name="catalog_download_is_running_in_background" comment="Notification that the resource catalog update is still downloading">Ἡ λήψη τοῦ καταλόγου συνεχίζεται· παρακαλοῦμε ξαναδοκιμάστε σὲ λίγο!</string>
|
||||
<string name="catalog_download_is_running_in_background" comment="Notification that the resource catalog update is still downloading">Ἡ μεταφόρτωση τοῦ καταλόγου συνεχίζεται· παρακαλοῦμε ξαναδοκιμάστε σὲ λίγο!</string>
|
||||
<!-- Context: Background download messages-->
|
||||
<string name="progress_message_checking_resource_is_running" comment="Progress notification that the check for a resource is still running ">Ἔλεγχος πόρου σὲ ἐξέλιξη</string>
|
||||
<string name="progress_message_checking_resource_is_running" comment="Progress notification that the check for a resource is still running ">Ἀναζήτηση πόρου σὲ ἐξέλιξη</string>
|
||||
<!-- Context: Background download messages-->
|
||||
<string name="keyboard_download_start_in_background" comment="Notification that a keyboard download has started">Ἡ λήψη τοῦ πληκτρολογίου ἔχει ξεκινήσει στὸ παρασκήνιο</string>
|
||||
<!-- Context: Background download messages-->
|
||||
|
|
@ -112,7 +112,7 @@
|
|||
<!-- Context: Background download messages -->
|
||||
<string name="download_failed" comment="Notification that a download failed">Ἡ λήψη ἀπέτυχε</string>
|
||||
<!-- Context: Background download messages -->
|
||||
<string name="failed_to_retrieve_file" comment="Failed to retrieve downloaded file">Ἀποτυχία ἀνακτήσεως ληφθέντος ἀρχείου</string>
|
||||
<string name="failed_to_retrieve_file" comment="Failed to retrieve downloaded file">Ἀποτυχία ἀνακλήσεως ληφθέντος ἀρχείου</string>
|
||||
<!-- Context: General Updates -->
|
||||
<string name="update_check_unavailable" comment="Error message when a Keyman server can't be reached">Ἀποτυχία προσβάσεως στὸν διακομιστή!</string>
|
||||
<!-- Context: General Updates -->
|
||||
|
|
@ -120,11 +120,11 @@
|
|||
<!-- Context: General Updates -->
|
||||
<string name="update_failed" comment="Notification that a resource update failed">Ἕνας ἢ περισσότεροι πόροι ἀπέτυχαν νὰ ἐνημερωθοῦν!</string>
|
||||
<!-- Context: General Updates. Removed in Keyman 17 -->
|
||||
<string name="update_success" comment="Notification that a resource update successfully updated">Οἱ πόροι ἐνημερώθηκαν ἐπιτυχῶς!</string>
|
||||
<string name="update_success" comment="Notification that a resource update successfully updated">Ὅλες οἱ ἐνημερώσεις ὁλοκληρώθηκαν ἐπιτυχῶς!</string>
|
||||
<!-- Context: Model Info -->
|
||||
<string name="model_version" comment="Title for a dictionary version">Ἔκδοση λεξικοῦ</string>
|
||||
<!-- Context: Model Info -->
|
||||
<string name="uninstall_model" comment="Label to uninstall a dictionary">Ἀπεγκαταστῆτε λεξικό</string>
|
||||
<string name="uninstall_model" comment="Label to uninstall a dictionary">Ἀπεγκαταστῆστε λεξικό</string>
|
||||
<!-- Context: Model Info -->
|
||||
<string name="confirm_delete_model" comment="Confirmation to delete a dictionary">Θὰ θέλατε νὰ διαγράψετε αὐτὸ τὸ λεξικό;</string>
|
||||
<!-- Context: Model Deleted -->
|
||||
|
|
@ -136,16 +136,16 @@
|
|||
<!-- Context: Language Settings menu strings -->
|
||||
<string name="enable_corrections" comment="Enable corrections from the suggestion banner">Ἐνεργοποιῆστε τὶς διορθώσεις</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<string name="enable_predictions" comment="Enable predictions from the suggestion banner">Ἐνεργοποιῆστε προβλέψεις</string>
|
||||
<string name="enable_predictions" comment="Enable predictions from the suggestion banner">Ἐνεργοποιῆστε προβλέψεις κειμένου</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<string name="model_label">Λεξικά</string>
|
||||
<string name="model_label">Λεξικό</string>
|
||||
<plurals name="model_count">
|
||||
<item quantity="one">Λεξικό</item>
|
||||
<item quantity="other">Λεξικά</item>
|
||||
</plurals>
|
||||
<!-- Context: Language Settings menu substring - Check for available dictionary -->
|
||||
<string name="check_available_model" comment="Check for available dictionary">Ἔλεγχος διαθεσίμου λεξικοῦ</string>
|
||||
<string name="check_model_online" comment="Check for dictionaries online">Ἔλεγχος λεξικῶν ὀνλάϊν</string>
|
||||
<string name="check_available_model" comment="Check for available dictionary">Ἀναζήτηση διαθεσίμου λεξικοῦ</string>
|
||||
<string name="check_model_online" comment="Check for dictionaries online">Ἀναζήτηση λεξικῶν ὀνλάϊν</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<string name="model_info_header" comment="Dictionary name">Λεξικό: %1$s</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
|
|
|
|||
36
build.sh
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env bash
|
||||
## 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
|
||||
|
||||
builder_describe "Keyman -- all projects" \
|
||||
":common" \
|
||||
":core" \
|
||||
":android" \
|
||||
":ios" \
|
||||
":linux" \
|
||||
":mac" \
|
||||
":web" \
|
||||
":windows" \
|
||||
":developer" \
|
||||
clean \
|
||||
configure \
|
||||
build \
|
||||
test \
|
||||
install \
|
||||
publish
|
||||
|
||||
builder_describe_platform \
|
||||
:android android-studio \
|
||||
:ios mac \
|
||||
:linux linux \
|
||||
:mac mac \
|
||||
:windows win
|
||||
|
||||
builder_parse "$@"
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
builder_run_child_actions clean configure build test publish install
|
||||
|
|
@ -16,6 +16,11 @@ builder_describe "Keyman common and resources modules" \
|
|||
build \
|
||||
test
|
||||
|
||||
builder_describe_platform \
|
||||
:linux linux \
|
||||
:mac mac \
|
||||
:windows win
|
||||
|
||||
builder_parse "$@"
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------------------
|
||||
|
|
|
|||
389
common/cpp/km_u16.cpp
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*
|
||||
* std::u16string functions and string conversion utility functions
|
||||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include "utfcodec.hpp"
|
||||
#include "km_u16.h"
|
||||
|
||||
/** string <- wstring
|
||||
* @brief Obtain a std::string from a std::wstring
|
||||
* @param wstr the std::wstring to be converted
|
||||
* @return a std::string
|
||||
*/
|
||||
std::string string_from_wstring(std::wstring const wstr) {
|
||||
return convert<wchar_t, char>((const std::wstring)wstr);
|
||||
}
|
||||
|
||||
/** wstring <- string
|
||||
* @brief Obtain a std::wstring from a std::string
|
||||
* @param str the std::string to be converted
|
||||
* @return a std::wstring
|
||||
*/
|
||||
std::wstring wstring_from_string(std::string const str) {
|
||||
return convert<char, wchar_t>((const std::string)str);
|
||||
}
|
||||
|
||||
/** u16string <- string
|
||||
* @brief Obtain a std::u16string from a std::string
|
||||
* @param str the std::string to be converted
|
||||
* @return a std::u16string
|
||||
*/
|
||||
std::u16string u16string_from_string(std::string const str) {
|
||||
return convert<char, char16_t>((const std::string)str);
|
||||
}
|
||||
|
||||
/** string <- u16string
|
||||
* @brief Obtain a std::string from a std::u16string
|
||||
* @param str16 the std::u16string to be converted
|
||||
* @return a std::string
|
||||
*/
|
||||
std::string string_from_u16string(std::u16string const str16) {
|
||||
return convert<char16_t, char>((const std::u16string)str16);
|
||||
}
|
||||
|
||||
/** wstring <- u16string
|
||||
* @brief Obtain a std::wstring from a std::u16string
|
||||
* @param str16 the std::u16string to be converted
|
||||
* @return a std::wstring
|
||||
*/
|
||||
std::wstring wstring_from_u16string(std::u16string const str16) {
|
||||
return convert<char16_t, wchar_t>((const std::u16string)str16);
|
||||
}
|
||||
|
||||
/** u16string <- wstring
|
||||
* @brief Obtain a std::u16string from a std::wstring
|
||||
* @param wstr the std::wstring to be converted
|
||||
* @return a std::u16string
|
||||
*/
|
||||
std::u16string u16string_from_wstring(std::wstring const wstr) {
|
||||
return convert<wchar_t, char16_t>((const std::wstring)wstr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert pointer to wchar_t to pointer to char16_t and copy sz elements into dst
|
||||
* @param dst destination
|
||||
* @param sz nr of characters to be copied
|
||||
* @param fmt source to convert and copy
|
||||
*/
|
||||
void u16sprintf(KMX_WCHAR* dst, const size_t sz, const wchar_t* fmt, ...) {
|
||||
wchar_t* wbuf = new wchar_t[sz];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vswprintf(wbuf, sz, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
std::u16string u16str = u16string_from_wstring(wbuf);
|
||||
u16ncpy(dst, u16str.c_str(), sz);
|
||||
delete[] wbuf;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert u16string to long integer
|
||||
* @param str u16string beginning with the representation of an integral number.
|
||||
* @param endptr Reference to the next character in str
|
||||
* @param base Numerical base (radix) that determines the valid characters and their interpretation
|
||||
* @return a long
|
||||
*/
|
||||
long int u16tol(const KMX_WCHAR* str, KMX_WCHAR** endptr, int base) {
|
||||
auto s = string_from_u16string(str);
|
||||
char* t;
|
||||
long int result = strtol(s.c_str(), &t, base);
|
||||
if (endptr != nullptr)
|
||||
*endptr = (KMX_WCHAR*)str + (t - s.c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string toHex(int num1) {
|
||||
if (num1 == 0)
|
||||
return "0";
|
||||
int num = num1;
|
||||
std::string s = "";
|
||||
while (num) {
|
||||
int temp = num % 16;
|
||||
if (temp <= 9)
|
||||
s += (48 + temp);
|
||||
else
|
||||
s += (87 + temp);
|
||||
num = num / 16;
|
||||
}
|
||||
std::reverse(s.begin(), s.end());
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Append max characters from u16string
|
||||
* @param dst Pointer to the destination array
|
||||
* @param src u16string to be appended
|
||||
* @param max Maximum number of characters to be appended.
|
||||
* @return Pointer to dst
|
||||
*/
|
||||
const KMX_WCHAR* u16ncat(KMX_WCHAR* dst, const KMX_WCHAR* src, size_t max) {
|
||||
KMX_WCHAR* o = dst;
|
||||
dst = (KMX_WCHAR*)u16chr(dst, 0);
|
||||
// max -= (dst-o);
|
||||
while (*src && max > 0) {
|
||||
*dst++ = *src++;
|
||||
max--;
|
||||
}
|
||||
if (max > 0)
|
||||
*dst = 0;
|
||||
return o;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Find last '/' or '\\' in an array of char16_t
|
||||
* @param name Pointer to the source
|
||||
* @return Pointer to the last slash/backslash
|
||||
*/
|
||||
const KMX_WCHAR* u16rchr_slash(KMX_WCHAR const* name) {
|
||||
const KMX_WCHAR* cp = NULL;
|
||||
cp = u16rchr(name, '\\');
|
||||
if (cp == NULL)
|
||||
cp = u16rchr(name, '/');
|
||||
return cp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Find last '/' or '\\' in an array of char
|
||||
* @param name Pointer to the source
|
||||
* @return Pointer to the last slash/backslash
|
||||
*/
|
||||
KMX_CHAR* strrchr_slash(KMX_CHAR* name) {
|
||||
KMX_CHAR* cp = NULL;
|
||||
cp = strrchr(name, '\\');
|
||||
if (cp == NULL)
|
||||
cp = strrchr(name, '/');
|
||||
return cp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Locate last occurrence of character in u16string
|
||||
* @param p Pointer to the source
|
||||
* @param ch The character to be found
|
||||
* @return A pointer to the last occurrence of character in u16str
|
||||
*/
|
||||
const KMX_WCHAR* u16rchr(const KMX_WCHAR* p, KMX_WCHAR ch) {
|
||||
const KMX_WCHAR* p_end = p + u16len(p) - 1;
|
||||
|
||||
if (ch == '\0')
|
||||
return p_end + 1;
|
||||
while (p_end >= p) {
|
||||
if (*p_end == ch)
|
||||
return p_end;
|
||||
p_end--;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Locate first occurrence of character in u16string
|
||||
* @param p Pointer to the source
|
||||
* @param ch The character to be found
|
||||
* @return A pointer to the first occurrence of character in u16str
|
||||
*/
|
||||
const KMX_WCHAR* u16chr(const KMX_WCHAR* p, KMX_WCHAR ch) {
|
||||
while (*p) {
|
||||
if (*p == ch)
|
||||
return p;
|
||||
p++;
|
||||
}
|
||||
return ch == 0 ? p : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy the u16string pointed to by scr into the array pointed to by dst
|
||||
* @param dst Pointer to the destination
|
||||
* @param src Pointer to the source to be copied
|
||||
* @return Pointer to dst
|
||||
*/
|
||||
const KMX_WCHAR* u16cpy(KMX_WCHAR* dst, const KMX_WCHAR* src) {
|
||||
KMX_WCHAR* o = dst;
|
||||
while (*src) {
|
||||
*dst++ = *src++;
|
||||
}
|
||||
*dst = 0;
|
||||
return o;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy max characters of the u16string pointed to by src into the array pointed by dst
|
||||
* @param dst Pointer to the destination
|
||||
* @param src Pointer to the source to be copied
|
||||
* @param max Maximum number of characters to be copied
|
||||
* @return Pointer to dst
|
||||
*/
|
||||
const KMX_WCHAR* u16ncpy(KMX_WCHAR* dst, const KMX_WCHAR* src, size_t max) {
|
||||
KMX_WCHAR* o = dst;
|
||||
while (*src && max > 0) {
|
||||
*dst++ = *src++;
|
||||
max--;
|
||||
}
|
||||
if (max > 0) {
|
||||
*dst = 0;
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the length of the u16string str
|
||||
* @param p Pointer to the source
|
||||
* @return The length of u16string
|
||||
*/
|
||||
size_t u16len(const KMX_WCHAR* p) {
|
||||
int i = 0;
|
||||
while (*p) {
|
||||
p++;
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compare two u16strings
|
||||
* @param p Pointer one u16string
|
||||
* @param q Pointer another u16string
|
||||
* @return 0 if strings are equal
|
||||
* ! = 0 if unequal
|
||||
*/
|
||||
int u16cmp(const KMX_WCHAR* p, const KMX_WCHAR* q) {
|
||||
while (*p && *q) {
|
||||
if (*p != *q)
|
||||
return *p - *q;
|
||||
p++;
|
||||
q++;
|
||||
}
|
||||
return *p - *q;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Case insensitive comparison of up to count characters in two strings
|
||||
* @param p Pointer one u16string
|
||||
* @param q Pointer another u16string
|
||||
* @param count Maximum number of characters to compare
|
||||
* @return 0 if strings are equal
|
||||
* ! = 0 if unequal
|
||||
*/
|
||||
int u16nicmp(const KMX_WCHAR* p, const KMX_WCHAR* q, size_t count) {
|
||||
while (*p && *q && count) {
|
||||
if (toupper(*p) != toupper(*q))
|
||||
return *p - *q;
|
||||
p++;
|
||||
q++;
|
||||
count--;
|
||||
}
|
||||
if (count)
|
||||
return *p - *q;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Case insensitive comparison of two strings
|
||||
* @param p Pointer one u16string
|
||||
* @param q Pointer another u16string
|
||||
* @return 0 if strings are equal
|
||||
* ! = 0 if unequal
|
||||
*/
|
||||
int u16icmp(const KMX_WCHAR* p, const KMX_WCHAR* q) {
|
||||
while (*p && *q) {
|
||||
if (toupper(*p) != toupper(*q))
|
||||
return *p - *q;
|
||||
p++;
|
||||
q++;
|
||||
}
|
||||
return *p - *q;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Comparison of up to count characters in two strings
|
||||
* @param p Pointer one u16string
|
||||
* @param q Pointer another u16string
|
||||
* @param count Maximum number of characters to compare
|
||||
* @return 0 if strings are equal
|
||||
* ! = 0 if unequal
|
||||
*/
|
||||
int u16ncmp(const KMX_WCHAR* p, const KMX_WCHAR* q, size_t count) {
|
||||
while (*p && *q && count) {
|
||||
if (*p != *q)
|
||||
return *p - *q;
|
||||
p++;
|
||||
q++;
|
||||
count--;
|
||||
}
|
||||
if (count)
|
||||
return *p - *q;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Split u16string into tokens
|
||||
* @param p Pointer to u16string to parse.
|
||||
* @param ch the delimiter character
|
||||
* @param ctx the remaining string after the first delimiter
|
||||
* @return Pointer to the first token in p
|
||||
*/
|
||||
KMX_WCHAR* u16tok(KMX_WCHAR* p, const KMX_WCHAR ch, KMX_WCHAR** ctx) {
|
||||
if (!p) {
|
||||
p = *ctx;
|
||||
if (!p)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
KMX_WCHAR* q = p;
|
||||
while (*q && *q != ch) {
|
||||
q++;
|
||||
}
|
||||
if (*q) {
|
||||
*q = 0;
|
||||
q++;
|
||||
while (*q == ch)
|
||||
q++;
|
||||
*ctx = q;
|
||||
} else {
|
||||
*ctx = NULL;
|
||||
}
|
||||
return *p ? p : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Split u16string into tokens
|
||||
* @param p Pointer to u16string to parse.
|
||||
* @param delimiters an array of delimiter characters
|
||||
* @param ctx the remaining string after the first delimiter
|
||||
* @return Pointer to the first token in p
|
||||
*/
|
||||
KMX_WCHAR* u16tok(KMX_WCHAR* p, const KMX_WCHAR* delimiters, KMX_WCHAR** ctx) {
|
||||
if (!p) {
|
||||
p = *ctx;
|
||||
if (!p)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
KMX_WCHAR* q = p;
|
||||
while (*q && !u16chr(delimiters, *q)) {
|
||||
q++;
|
||||
}
|
||||
if (*q) {
|
||||
*q = 0;
|
||||
q++;
|
||||
while (*q && u16chr(delimiters, *q))
|
||||
q++;
|
||||
*ctx = q;
|
||||
} else {
|
||||
*ctx = NULL;
|
||||
}
|
||||
return *p ? p : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a u16string to a double
|
||||
* @param str Pointer to u16string
|
||||
* @return double value equivalent to the string
|
||||
*/
|
||||
double u16tof(KMX_WCHAR* str16) {
|
||||
char* pEnd;
|
||||
std::string str = string_from_u16string(str16);
|
||||
return strtof(str.c_str(), &pEnd);
|
||||
}
|
||||
76
common/include/km_u16.h
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <km_types.h>
|
||||
|
||||
/** @brief Obtain a std::string from a std::wstring */
|
||||
std::string string_from_wstring(std::wstring const wstr);
|
||||
|
||||
/** @brief Obtain a std::wstring from a std::string */
|
||||
std::wstring wstring_from_string(std::string const str);
|
||||
|
||||
/** @brief Obtain a std::u16string from a std::string */
|
||||
std::u16string u16string_from_string(std::string const str);
|
||||
|
||||
/** @brief Obtain a std::string from a std::u16string */
|
||||
std::string string_from_u16string(std::u16string const str16);
|
||||
|
||||
/** @brief Obtain a std::wstring from a std::u16string */
|
||||
std::wstring wstring_from_u16string(std::u16string const str16);
|
||||
|
||||
/** @brief Obtain a std::u16string from a std::wstring */
|
||||
std::u16string u16string_from_wstring(std::wstring const wstr);
|
||||
|
||||
/** @brief Convert pointer to wchar_t to pointer to char16_t and copy sz elements into dst */
|
||||
void u16sprintf(KMX_WCHAR* dst, const size_t sz, const wchar_t* fmt, ...);
|
||||
|
||||
/** @brief Return the length of the u16string str */
|
||||
size_t u16len(const KMX_WCHAR* p);
|
||||
|
||||
/** @brief Compare two u16strings */
|
||||
int u16cmp(const KMX_WCHAR* p, const KMX_WCHAR* q);
|
||||
|
||||
/** @brief Case insensitive comparison of two strings */
|
||||
int u16icmp(const KMX_WCHAR* p, const KMX_WCHAR* q);
|
||||
|
||||
/** @brief Comparison of up to count characters in two strings */
|
||||
int u16ncmp(const KMX_WCHAR* p, const KMX_WCHAR* q, size_t count);
|
||||
|
||||
/** @brief Case insensitive comparison of up to count characters in two strings */
|
||||
int u16nicmp(const KMX_WCHAR* p, const KMX_WCHAR* q, size_t count);
|
||||
|
||||
/** @brief Copy max characters of the u16string pointed to by src into the array pointed to by dst */
|
||||
const KMX_WCHAR* u16ncpy(KMX_WCHAR* dst, const KMX_WCHAR* src, size_t max);
|
||||
|
||||
/** @brief Copy the u16string pointed to by src into the array pointed to by dst */
|
||||
const KMX_WCHAR* u16cpy(KMX_WCHAR* dst, const KMX_WCHAR* src);
|
||||
|
||||
/** @brief Locate last occurrence of character in u16string */
|
||||
const KMX_WCHAR* u16rchr(const KMX_WCHAR* p, KMX_WCHAR ch);
|
||||
|
||||
/** @brief Locate first occurrence of character in u16string */
|
||||
const KMX_WCHAR* u16chr(const KMX_WCHAR* p, KMX_WCHAR ch);
|
||||
|
||||
/** @brief Append max characters from u16string */
|
||||
const KMX_WCHAR* u16ncat(KMX_WCHAR* dst, const KMX_WCHAR* src, size_t max);
|
||||
|
||||
/** @brief Split u16string into tokens */
|
||||
KMX_WCHAR* u16tok(KMX_WCHAR* p, const KMX_WCHAR ch, KMX_WCHAR** ctx);
|
||||
|
||||
/** @brief Split u16string into tokens */
|
||||
KMX_WCHAR* u16tok(KMX_WCHAR* p, const KMX_WCHAR* delimiters, KMX_WCHAR** ctx);
|
||||
|
||||
/** @brief Convert a u16string to a double */
|
||||
long int u16tol(const KMX_WCHAR* str, KMX_WCHAR** endptr, int base);
|
||||
|
||||
/** @brief Convert a u16string to a double */
|
||||
double u16tof(KMX_WCHAR* str);
|
||||
|
||||
/** @brief find last '/' or '\\' in an array of char */
|
||||
KMX_CHAR* strrchr_slash(KMX_CHAR* Name);
|
||||
|
||||
/** @brief find last '/' or '\\' in an array of char16_t */
|
||||
const KMX_WCHAR* u16rchr_slash(KMX_WCHAR const* Name);
|
||||
|
||||
std::string toHex(int num1);
|
||||
|
|
@ -3,5 +3,5 @@ export {
|
|||
transformToSuggestion, defaultApplyCasing
|
||||
} from "./common.js";
|
||||
export { default as QuoteBehavior } from "./quote-behavior.js";
|
||||
export { Tokenization, tokenize, getLastPreCaretToken, wordbreak } from "./tokenization.js";
|
||||
export { getLastPreCaretToken, Token, Tokenization, tokenize, wordbreak } from "./tokenization.js";
|
||||
export { default as TrieModel, TrieModelOptions } from "./trie-model.js";
|
||||
|
|
@ -1,18 +1,23 @@
|
|||
// While we _could_ define this within @keymanapp/models-wordbreakers instead, it's probably
|
||||
// better to leave that package as _just_ the wordbreakers.
|
||||
|
||||
export interface Token {
|
||||
text: string,
|
||||
isWhitespace?: boolean
|
||||
}
|
||||
|
||||
export interface Tokenization {
|
||||
/**
|
||||
* An array of tokens to the left of the caret. If the caret is in the middle of a token,
|
||||
* only the part to the left of the caret is included.
|
||||
*/
|
||||
left: USVString[],
|
||||
left: Token[],
|
||||
|
||||
/**
|
||||
* An array of tokens to the right of the caret. If the caret is in the middle of a token,
|
||||
* only the part to the right of the caret is included.
|
||||
*/
|
||||
right: USVString[],
|
||||
right: Token[],
|
||||
|
||||
/**
|
||||
* A flag indicating whether or not the caret's position in the context caused a token
|
||||
|
|
@ -22,21 +27,74 @@ export interface Tokenization {
|
|||
caretSplitsToken: boolean
|
||||
}
|
||||
|
||||
export function tokenize(wordBreaker: WordBreakingFunction, context?: Partial<Context>): Tokenization {
|
||||
export function tokenize(
|
||||
wordBreaker: WordBreakingFunction,
|
||||
context: Partial<Context>,
|
||||
options?: {
|
||||
/** Characters to rejoin to preceding tokens if found immediately pre-caret. */
|
||||
rejoins?: string[]
|
||||
}
|
||||
): Tokenization {
|
||||
// The Unicode word-breaker algorithm looks for places where it's "safe" to
|
||||
// split a word across lines, operating upon _completed_ words. There are
|
||||
// some cases where, if placed mid-word, it would add a boundary that does not
|
||||
// exist at the end of the word. The single-quote character is one such
|
||||
// location - it's hard to tell if `can'` is the end of a quote or the prefix
|
||||
// to `can't`. So, if `'` is immediately pre-caret, we "rejoin" it.
|
||||
const rejoins = options?.rejoins || ["'"];
|
||||
context = context || {
|
||||
left: undefined,
|
||||
startOfBuffer: undefined,
|
||||
endOfBuffer: undefined
|
||||
};
|
||||
|
||||
let leftSpans = wordBreaker(context.left || '') || [];
|
||||
let rightSpans = wordBreaker(context.right || '') || [];
|
||||
const leftSpans = wordBreaker(context.left || '') || [];
|
||||
const rightSpans = wordBreaker(context.right || '') || [];
|
||||
|
||||
let leftTail: Span;
|
||||
if(leftSpans.length > 0) {
|
||||
leftTail = leftSpans[leftSpans.length - 1];
|
||||
const tokenization: Tokenization = {
|
||||
left: [],
|
||||
right: [],
|
||||
caretSplitsToken: false
|
||||
}
|
||||
|
||||
// New step 1: process left-context.
|
||||
let currentIndex = 0;
|
||||
while(leftSpans.length > 0) {
|
||||
const nextSpan = leftSpans[0];
|
||||
if(Math.max(nextSpan.start, currentIndex) != currentIndex) {
|
||||
const nextIndex = Math.max(currentIndex, nextSpan.start);
|
||||
// Implicit whitespace span!
|
||||
tokenization.left.push({
|
||||
text: context.left!.substring(currentIndex, nextIndex),
|
||||
isWhitespace: true
|
||||
});
|
||||
currentIndex = nextIndex;
|
||||
} else {
|
||||
leftSpans.shift();
|
||||
// Explicit non-whitespace span.
|
||||
tokenization.left.push({
|
||||
text: nextSpan.text
|
||||
});
|
||||
currentIndex = Math.max(currentIndex, nextSpan.end);
|
||||
}
|
||||
}
|
||||
|
||||
// Detect any pre-caret whitespace after the final pre-caret non-whitespace
|
||||
// token
|
||||
//
|
||||
// Note: the default wordbreaker won't need this code, as it emits a `''`
|
||||
// after final whitespace.
|
||||
if(context.left != null && currentIndex != context.left.length) {
|
||||
const nextIndex = Math.max(currentIndex, context.left!.length);
|
||||
tokenization.left.push({
|
||||
text: context.left.substring(currentIndex, nextIndex),
|
||||
isWhitespace: true
|
||||
});
|
||||
currentIndex = nextIndex;
|
||||
}
|
||||
|
||||
// New step 2: handle any rejoins needed.
|
||||
|
||||
// Handle any desired special handling for directly-pre-caret scenarios - where for this
|
||||
// _specific_ context, we should not make a token division where one normally would exist otherwise.
|
||||
//
|
||||
|
|
@ -45,62 +103,88 @@ export function tokenize(wordBreaker: WordBreakingFunction, context?: Partial<Co
|
|||
// But, if the user is editing text and the caret is directly after a caret, there's a notable
|
||||
// chance they may in the middle of typing a contraction. Refer to
|
||||
// https://github.com/keymanapp/keyman/issues/6572.
|
||||
if(leftSpans.length > 1) {
|
||||
const leftTailBase = leftSpans[leftSpans.length - 2];
|
||||
let leftTokenCount = tokenization.left.length;
|
||||
if(leftTokenCount > 1) {
|
||||
const leftTailBase = tokenization.left[leftTokenCount - 2];
|
||||
const leftTail = tokenization.left[leftTokenCount - 1];
|
||||
|
||||
// If the final two pre-caret spans are adjacent - without intervening whitespace...
|
||||
if(leftTailBase.end == leftTail!.start) {
|
||||
if(!leftTailBase.isWhitespace && !leftTail.isWhitespace) {
|
||||
// Ideal: if(leftTailBase is standard-char-class && leftTail is single-quote-class)
|
||||
// But we don't have character class access here; it's all wordbreaker-function internal.
|
||||
// Upon inspection of the wordbreaker data definitions... the single-quote-class is ONLY "'".
|
||||
// So... we'll just be lazy for now and append the `'`.
|
||||
if(leftTail!.text == "'") {
|
||||
let mergedSpan: Span = {
|
||||
text: leftTailBase.text + leftTail!.text,
|
||||
start: leftTailBase.start,
|
||||
end: leftTail!.end,
|
||||
length: leftTailBase.length + leftTail!.length
|
||||
};
|
||||
|
||||
leftSpans.pop(); // leftTail
|
||||
leftSpans.pop(); // leftTailBase
|
||||
leftSpans.push(mergedSpan);
|
||||
leftTail = mergedSpan; // don't forget to update the `leftTail` Span!
|
||||
if(rejoins.indexOf(leftTail!.text) != -1) {
|
||||
tokenization.left.pop(); // leftTail
|
||||
tokenization.left.pop(); // leftTailBase
|
||||
tokenization.left.push({
|
||||
text: leftTailBase.text + leftTail.text
|
||||
});
|
||||
leftTokenCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// With any 'detokenization' cases already handled, we may now begin to build the return object.
|
||||
let tokenization: Tokenization = {
|
||||
left: leftSpans.map(span => span.text),
|
||||
right: rightSpans.map(span => span.text),
|
||||
// New step 3: right-context tokenization + token split detection
|
||||
|
||||
// A default initialization of the value.
|
||||
caretSplitsToken: false
|
||||
};
|
||||
// context.right starts from index 0; it's an 'index reset'.
|
||||
currentIndex = 0;
|
||||
// Set a flag for special "first token" processing.
|
||||
let firstRightToken = true;
|
||||
|
||||
// Now the hard part - determining whether or not the caret caused a token split.
|
||||
if(leftSpans.length > 0 && rightSpans.length > 0) {
|
||||
let rightHead = rightSpans[0];
|
||||
// Note: is MOSTLY "WET" with the left-span loop, though the
|
||||
// `caretSplitsToken` check is additional.
|
||||
while(rightSpans.length > 0) {
|
||||
const nextSpan = rightSpans[0];
|
||||
if(Math.max(nextSpan.start, currentIndex) != currentIndex) {
|
||||
const nextIndex = Math.max(currentIndex, nextSpan.start);
|
||||
// Implicit whitespace span!
|
||||
tokenization.right.push({
|
||||
text: context.right!.substring(currentIndex, nextIndex),
|
||||
isWhitespace: true
|
||||
});
|
||||
currentIndex = nextIndex;
|
||||
} else {
|
||||
const leftTail = tokenization.left[leftTokenCount-1];
|
||||
if(leftTail) {
|
||||
// If the first non-whitespace token to the right is non-whitespace,
|
||||
// and the last token to the left is non-whitespace, the caret may
|
||||
// be splitting a token.
|
||||
if(firstRightToken && !leftTail.isWhitespace) {
|
||||
if(wordBreaker(leftTail!.text + nextSpan.text).length == 1) {
|
||||
tokenization.caretSplitsToken = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If tokenization includes all characters on each side of the caret,
|
||||
// we have a good candidate for a caret-splitting scenario.
|
||||
let leftSuffixWordbreak = leftTail!.end != context.left!.length;
|
||||
let rightPrefixWordbreak = rightHead.start != 0;
|
||||
|
||||
if(leftSuffixWordbreak || rightPrefixWordbreak) {
|
||||
// Bypass the final test, as we already know the caret didn't split a token.
|
||||
// (The tokenization process already removed characters between the two.)
|
||||
return tokenization;
|
||||
// Explicit non-whitespace span.
|
||||
rightSpans.shift();
|
||||
tokenization.right.push({
|
||||
text: nextSpan.text
|
||||
});
|
||||
currentIndex = Math.max(currentIndex, nextSpan.end);
|
||||
}
|
||||
|
||||
// Worth note - some languages don't use wordbreaking characters. So, a final check:
|
||||
//
|
||||
// Does the wordbreaker split a merge of the 'two center' tokens?
|
||||
// If not, then the caret is responsible for the split.
|
||||
if(wordBreaker(leftTail!.text + rightHead.text).length == 1) {
|
||||
tokenization.caretSplitsToken = true;
|
||||
}
|
||||
// We've always processed the "first right token" after the first iteration.
|
||||
// Do not run the caret-split check on any future iterations.
|
||||
firstRightToken = false;
|
||||
}
|
||||
|
||||
// Detect any pre-caret whitespace after the final pre-caret non-whitespace
|
||||
// token
|
||||
//
|
||||
// Note: the default wordbreaker won't need this code, as it emits a `''`
|
||||
// after final whitespace.
|
||||
//
|
||||
// Also note: is pretty much WET with the similar check after the
|
||||
// leftSpan loop.
|
||||
if(context.right && currentIndex != context.right.length) {
|
||||
const nextIndex = Math.max(currentIndex, context.right!.length);
|
||||
tokenization.right.push({
|
||||
text: context.right.substring(currentIndex, nextIndex),
|
||||
isWhitespace: true
|
||||
});
|
||||
currentIndex = nextIndex;
|
||||
}
|
||||
|
||||
return tokenization;
|
||||
|
|
@ -108,12 +192,18 @@ export function tokenize(wordBreaker: WordBreakingFunction, context?: Partial<Co
|
|||
|
||||
/**
|
||||
* Get the last word of the phrase before the caret or nothing.
|
||||
* If the last 'token' before the caret is whitespace, returns `''`.
|
||||
* @param fullLeftContext the entire left context of the string.
|
||||
*/
|
||||
export function getLastPreCaretToken(wordBreaker: WordBreakingFunction, context: Context): string {
|
||||
let tokenization = tokenize(wordBreaker, context);
|
||||
if (tokenization.left.length > 0) {
|
||||
return tokenization.left.pop() as string;
|
||||
const lastToken = tokenization.left.pop();
|
||||
if(lastToken!.isWhitespace) {
|
||||
return '';
|
||||
} else {
|
||||
return lastToken!.text;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
|
|
|
|||
|
|
@ -98,13 +98,9 @@ class Traversal implements LexiconTraversal {
|
|||
}
|
||||
|
||||
child(char: USVString): LexiconTraversal | undefined {
|
||||
/*
|
||||
Note: would otherwise return the current instance if `char == ''`. If
|
||||
such a call is happening, it's probably indicative of an implementation
|
||||
issue elsewhere - let's signal now in order to catch such stuff early.
|
||||
*/
|
||||
// May result for blank tokens resulting immediately after whitespace.
|
||||
if(char == '') {
|
||||
return undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
// Split into individual code units.
|
||||
|
|
|
|||
128
common/models/templates/test/custom-breakers.def.js
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/**
|
||||
* The custom wordbreaker used by the sil.km.gcc model as of
|
||||
* https://github.com/keymanapp/lexical-models/pull/265.
|
||||
* @type {WordBreakingFunction}
|
||||
* */
|
||||
export function customWordBreakerProper(str) {
|
||||
const whitespaceRegex = /\s|\u200b|\n|\r/;
|
||||
const tokens = str.split(whitespaceRegex);
|
||||
|
||||
for(let i=0; i < tokens.length; i++) {
|
||||
const token = tokens[i];
|
||||
if(token.length == 0) {
|
||||
tokens.splice(i, 1);
|
||||
i--;
|
||||
continue;
|
||||
} else if(token.length == 1 && whitespaceRegex.test(token)) {
|
||||
tokens.splice(i, 1);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Certain punctuation marks should be considered a separate token from the word they're next to.
|
||||
const punctuationMarks = ['«', '»', '$', '#' /* add extras here */];
|
||||
const punctSplitIndices = [];
|
||||
|
||||
// Find if and where each mark exists within the token
|
||||
for(let i = 0; i < punctuationMarks.length; i++) {
|
||||
const split = token.indexOf(punctuationMarks[i]);
|
||||
if(split >= 0) {
|
||||
punctSplitIndices.push(split);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort and pick the earliest mark's location. If none exists, use -1.
|
||||
punctSplitIndices.sort();
|
||||
const splitPoint = punctSplitIndices[0] === undefined ? -1 : punctSplitIndices[0];
|
||||
|
||||
if(splitPoint > -1) {
|
||||
const left = token.substring(0, splitPoint); // (0, -1) => ''
|
||||
const punct = token.substring(splitPoint, splitPoint+1);
|
||||
const right = token.substring(splitPoint+1); // Starting past the end of the string => ''
|
||||
|
||||
if(left) {
|
||||
tokens.splice(i++, 0, left);
|
||||
}
|
||||
tokens.splice(i++, 1, punct);
|
||||
if(right) {
|
||||
tokens.splice(i, 0, right);
|
||||
}
|
||||
// Ensure that the next iteration puts `i` immediately after the punctuation token... even if
|
||||
// there was a `right` portion, as it may have extra marks that also need to be spun off.
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
let latestIndex = 0;
|
||||
return tokens.map(function(token) {
|
||||
const start = str.indexOf(token, latestIndex);
|
||||
latestIndex = start + token.length;
|
||||
return {
|
||||
left: start,
|
||||
start: start,
|
||||
right: start + token.length,
|
||||
end: start + token.length,
|
||||
length: token.length,
|
||||
text: token
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The version of the custom wordbreaker used by the sil.km.gcc model
|
||||
* before https://github.com/keymanapp/lexical-models/pull/265, which
|
||||
* triggered #12200.
|
||||
* @type {WordBreakingFunction}
|
||||
* */
|
||||
export function customWordBreakerFormer (str) {
|
||||
const tokens = str.split(/\s|\u200b/);
|
||||
|
||||
for(let i=0; i < tokens.length; i++) {
|
||||
const token = tokens[i];
|
||||
if(token.length == 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Certain punctuation marks should be considered a separate token from the word they're next to.
|
||||
const punctuationMarks = ['«', '»', '$', '#' /* add extras here */];
|
||||
const punctSplitIndices = [];
|
||||
|
||||
// Find if and where each mark exists within the token
|
||||
for(let i = 0; i < punctuationMarks.length; i++) {
|
||||
const split = token.indexOf(punctuationMarks[i]);
|
||||
if(split >= 0) {
|
||||
punctSplitIndices.push(split);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort and pick the earliest mark's location. If none exists, use -1.
|
||||
punctSplitIndices.sort();
|
||||
const splitPoint = punctSplitIndices[0] === undefined ? -1 : punctSplitIndices[0];
|
||||
|
||||
if(splitPoint > -1) {
|
||||
const left = token.substring(0, splitPoint); // (0, -1) => ''
|
||||
const punct = token.substring(splitPoint, splitPoint+1);
|
||||
const right = token.substring(splitPoint+1); // Starting past the end of the string => ''
|
||||
|
||||
if(left) {
|
||||
tokens.splice(i++, 0, left);
|
||||
}
|
||||
tokens.splice(i++, 1, punct);
|
||||
if(right) {
|
||||
tokens.splice(i, 0, right);
|
||||
}
|
||||
// Ensure that the next iteration puts `i` immediately after the punctuation token... even if
|
||||
// there was a `right` portion, as it may have extra marks that also need to be spun off.
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return tokens.map(function(token) {
|
||||
return {
|
||||
left: str.indexOf(token),
|
||||
start: str.indexOf(token),
|
||||
right: str.indexOf(token) + token.length,
|
||||
end: str.indexOf(token) + token.length,
|
||||
text: token
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -5,6 +5,29 @@
|
|||
import { assert } from 'chai';
|
||||
import * as models from "@keymanapp/models-templates";
|
||||
import * as wordBreakers from "@keymanapp/models-wordbreakers";
|
||||
import { customWordBreakerFormer, customWordBreakerProper } from './custom-breakers.def.js';
|
||||
|
||||
function asProcessedToken(text) {
|
||||
// default wordbreaker emits these at the end of each context half if ending with whitespace.
|
||||
// Indicates a new spot for non-whitespace text.
|
||||
if(text == '') {
|
||||
return {
|
||||
text: text
|
||||
};
|
||||
} else if(text.trim() == '') {
|
||||
// Simple cases using standard Latin-script patterns - can be handled via trim()
|
||||
return {
|
||||
text: text,
|
||||
isWhitespace: true
|
||||
};
|
||||
}
|
||||
|
||||
// could add simple check for other, non-default cases here.
|
||||
|
||||
return {
|
||||
text: text
|
||||
};
|
||||
}
|
||||
|
||||
describe('Tokenization functions', function() {
|
||||
describe('tokenize', function() {
|
||||
|
|
@ -19,8 +42,8 @@ describe('Tokenization functions', function() {
|
|||
let tokenization = models.tokenize(wordBreakers.default, context);
|
||||
|
||||
let expectedResult = {
|
||||
left: ['The', 'quick', 'brown', 'fox'],
|
||||
right: ['jumped', 'over', 'the', 'lazy', 'dog'],
|
||||
left: ['The', ' ', 'quick', ' ', 'brown', ' ', 'fox'].map(asProcessedToken),
|
||||
right: [' ', 'jumped', ' ', 'over', ' ', 'the', ' ', 'lazy', ' ', 'dog'].map(asProcessedToken),
|
||||
caretSplitsToken: false
|
||||
};
|
||||
|
||||
|
|
@ -30,7 +53,7 @@ describe('Tokenization functions', function() {
|
|||
it('tokenizes English using defaults, pre-whitespace caret, partial context', function() {
|
||||
let context = {
|
||||
left: "quick brown fox", // No "The"
|
||||
right: " jumped over the lazy", // No "dog"
|
||||
right: " jumped over the lazy ", // No "dog"
|
||||
startOfBuffer: false,
|
||||
endOfBuffer: false
|
||||
};
|
||||
|
|
@ -38,8 +61,8 @@ describe('Tokenization functions', function() {
|
|||
let tokenization = models.tokenize(wordBreakers.default, context);
|
||||
|
||||
let expectedResult = {
|
||||
left: ['quick', 'brown', 'fox'],
|
||||
right: ['jumped', 'over', 'the', 'lazy'],
|
||||
left: ['quick', ' ', 'brown', ' ', 'fox'].map(asProcessedToken),
|
||||
right: [' ', 'jumped', ' ', 'over', ' ', 'the', ' ', 'lazy', ' ', ''].map(asProcessedToken),
|
||||
caretSplitsToken: false
|
||||
};
|
||||
|
||||
|
|
@ -59,14 +82,35 @@ describe('Tokenization functions', function() {
|
|||
// Technically, we're editing the start of the first token on the right
|
||||
// when in this context.
|
||||
let expectedResult = {
|
||||
left: ['The', 'quick', 'brown', 'fox', ''],
|
||||
right: ['jumped', 'over', 'the', 'lazy', 'dog'],
|
||||
left: ['The', ' ', 'quick', ' ', 'brown', ' ', 'fox', ' ', ''].map(asProcessedToken),
|
||||
right: ['jumped', ' ', 'over', ' ', 'the', ' ', 'lazy', ' ', 'dog'].map(asProcessedToken),
|
||||
caretSplitsToken: true
|
||||
};
|
||||
|
||||
assert.deepEqual(tokenization, expectedResult);
|
||||
});
|
||||
|
||||
it('tokenizes English using ascii-breaker, post-whitespace caret', function() {
|
||||
let context = {
|
||||
left: "The quick brown fox ",
|
||||
right: "jumped over the lazy dog ",
|
||||
startOfBuffer: true,
|
||||
endOfBuffer: true
|
||||
};
|
||||
|
||||
let tokenization = models.tokenize(wordBreakers.ascii, context);
|
||||
|
||||
// Technically, we're editing the start of the first token on the right
|
||||
// when in this context.
|
||||
let expectedResult = {
|
||||
left: ['The', ' ', 'quick', ' ', 'brown', ' ', 'fox', ' '].map(asProcessedToken),
|
||||
right: ['jumped', ' ', 'over', ' ', 'the', ' ', 'lazy', ' ', 'dog', ' '].map(asProcessedToken),
|
||||
caretSplitsToken: false
|
||||
};
|
||||
|
||||
assert.deepEqual(tokenization, expectedResult);
|
||||
});
|
||||
|
||||
it('tokenizes English using defaults, post-whitespace caret, partial context', function() {
|
||||
let context = {
|
||||
left: "quick brown fox ",
|
||||
|
|
@ -80,8 +124,8 @@ describe('Tokenization functions', function() {
|
|||
// Technically, we're editing the start of the first token on the right
|
||||
// when in this context.
|
||||
let expectedResult = {
|
||||
left: ['quick', 'brown', 'fox', ''],
|
||||
right: ['jumped', 'over', 'the', 'lazy'],
|
||||
left: ['quick', ' ', 'brown', ' ', 'fox', ' ', ''].map(asProcessedToken),
|
||||
right: ['jumped', ' ', 'over', ' ', 'the', ' ', 'lazy'].map(asProcessedToken),
|
||||
caretSplitsToken: true
|
||||
};
|
||||
|
||||
|
|
@ -99,8 +143,8 @@ describe('Tokenization functions', function() {
|
|||
let tokenization = models.tokenize(wordBreakers.default, context);
|
||||
|
||||
let expectedResult = {
|
||||
left: ['The', 'quick', 'brown', 'fox', 'jum'],
|
||||
right: ['ped', 'over', 'the', 'lazy', 'dog'],
|
||||
left: ['The', ' ', 'quick', ' ', 'brown', ' ', 'fox', ' ', 'jum'].map(asProcessedToken),
|
||||
right: ['ped', ' ', 'over', ' ', 'the', ' ', 'lazy', ' ', 'dog'].map(asProcessedToken),
|
||||
caretSplitsToken: true
|
||||
};
|
||||
|
||||
|
|
@ -118,15 +162,15 @@ describe('Tokenization functions', function() {
|
|||
let tokenization = models.tokenize(wordBreakers.default, context);
|
||||
|
||||
let expectedResult = {
|
||||
left: ['The', 'quick', 'brown', 'fox', 'jum'],
|
||||
right: ['ped', 'over', 'the', 'lazy', 'dog'],
|
||||
left: ['The', ' ', 'quick', ' ', 'brown', ' ', 'fox', ' ', 'jum'].map(asProcessedToken),
|
||||
right: ['ped', ' ', 'over', ' ', 'the', ' ', 'lazy', ' ', 'dog'].map(asProcessedToken),
|
||||
caretSplitsToken: true
|
||||
};
|
||||
|
||||
assert.deepEqual(tokenization, expectedResult);
|
||||
});
|
||||
|
||||
it('empty context case', function() {
|
||||
it('properly handles empty-context cases', function() {
|
||||
// Wordbreaking on a empty space => no word.
|
||||
let context = {
|
||||
left: '', startOfBuffer: true,
|
||||
|
|
@ -144,7 +188,7 @@ describe('Tokenization functions', function() {
|
|||
assert.deepEqual(tokenization, expectedResult);
|
||||
});
|
||||
|
||||
it('nil context case', function() {
|
||||
it('properly handles null context cases', function() {
|
||||
// Wordbreaking on a empty space => no word.
|
||||
let tokenization = models.tokenize(wordBreakers.default, null);
|
||||
|
||||
|
|
@ -157,7 +201,7 @@ describe('Tokenization functions', function() {
|
|||
assert.deepEqual(tokenization, expectedResult);
|
||||
});
|
||||
|
||||
it('near-empty context: one space before caret', function() {
|
||||
it('properly handles a near-empty context: one space before caret', function() {
|
||||
// Wordbreaking on a empty space => no word.
|
||||
let context = {
|
||||
left: ' ', startOfBuffer: true,
|
||||
|
|
@ -167,7 +211,7 @@ describe('Tokenization functions', function() {
|
|||
let tokenization = models.tokenize(wordBreakers.default, context);
|
||||
|
||||
let expectedResult = {
|
||||
left: [''],
|
||||
left: [' ', ''].map(asProcessedToken),
|
||||
right: [],
|
||||
caretSplitsToken: false
|
||||
};
|
||||
|
|
@ -175,6 +219,85 @@ describe('Tokenization functions', function() {
|
|||
assert.deepEqual(tokenization, expectedResult);
|
||||
});
|
||||
|
||||
it('properly tokenizes partial English contractions - default setting', function() {
|
||||
let context = {
|
||||
left: "I can'",
|
||||
right: "",
|
||||
startOfBuffer: true,
|
||||
endOfBuffer: true
|
||||
};
|
||||
|
||||
let tokenization = models.tokenize(wordBreakers.default, context);
|
||||
|
||||
// Technically, we're editing the start of the first token on the right
|
||||
// when in this context.
|
||||
let expectedResult = {
|
||||
left: ['I', ' ', 'can\''].map(asProcessedToken),
|
||||
right: [].map(asProcessedToken),
|
||||
caretSplitsToken: false
|
||||
};
|
||||
|
||||
assert.deepEqual(tokenization, expectedResult);
|
||||
});
|
||||
|
||||
it('overly tokenizes partial English contractions when (default) apostrophe rejoin is disabled', function() {
|
||||
let context = {
|
||||
left: "I can'",
|
||||
right: "",
|
||||
startOfBuffer: true,
|
||||
endOfBuffer: true
|
||||
};
|
||||
|
||||
let tokenization = models.tokenize(wordBreakers.default, context, { rejoins: [] });
|
||||
|
||||
// Technically, we're editing the start of the first token on the right
|
||||
// when in this context.
|
||||
let expectedResult = {
|
||||
left: ['I', ' ', 'can' , '\''].map(asProcessedToken),
|
||||
right: [].map(asProcessedToken),
|
||||
caretSplitsToken: false
|
||||
};
|
||||
|
||||
assert.deepEqual(tokenization, expectedResult);
|
||||
});
|
||||
|
||||
it('properly tokenizes English contractions', function() {
|
||||
// Note: a 'context0' with the caret before the `'` actually
|
||||
// is not supported well yet; a leading `'` is broken from
|
||||
// following text when in isolation.
|
||||
|
||||
let context1 = {
|
||||
left: "I can'",
|
||||
right: "t",
|
||||
startOfBuffer: true,
|
||||
endOfBuffer: true
|
||||
};
|
||||
|
||||
let context2 = {
|
||||
left: "I can't",
|
||||
right: "",
|
||||
startOfBuffer: true,
|
||||
endOfBuffer: true
|
||||
}
|
||||
|
||||
let tokenization1 = models.tokenize(wordBreakers.default, context1);
|
||||
let tokenization2 = models.tokenize(wordBreakers.default, context2);
|
||||
|
||||
let expectedResult1 = {
|
||||
left: ['I', ' ', 'can\''].map(asProcessedToken),
|
||||
right: ['t'].map(asProcessedToken),
|
||||
caretSplitsToken: true
|
||||
};
|
||||
let expectedResult2 = {
|
||||
left: ['I', ' ', 'can\'t'].map(asProcessedToken),
|
||||
right: [].map(asProcessedToken),
|
||||
caretSplitsToken: false
|
||||
};
|
||||
|
||||
assert.deepEqual(tokenization1, expectedResult1);
|
||||
assert.deepEqual(tokenization2, expectedResult2);
|
||||
});
|
||||
|
||||
// For the next few tests: a mocked wordbreaker for Khmer, a language
|
||||
// without whitespace between words.
|
||||
let mockedKhmerBreaker = function(text) {
|
||||
|
|
@ -258,8 +381,8 @@ describe('Tokenization functions', function() {
|
|||
let tokenization = models.tokenize(mockedKhmerBreaker, context);
|
||||
|
||||
let expectedResult = {
|
||||
left: ['ស្រុក'],
|
||||
right: ['ខ្មែរ'],
|
||||
left: ['ស្រុក'].map(asProcessedToken),
|
||||
right: ['ខ្មែរ'].map(asProcessedToken),
|
||||
caretSplitsToken: false
|
||||
};
|
||||
|
||||
|
|
@ -282,8 +405,8 @@ describe('Tokenization functions', function() {
|
|||
let tokenization = models.tokenize(mockedKhmerBreaker, context);
|
||||
|
||||
let expectedResult = {
|
||||
left: ['ស្រុ'],
|
||||
right: ['ក', 'ខ្មែរ'],
|
||||
left: ['ស្រុ'].map(asProcessedToken),
|
||||
right: ['ក', 'ខ្មែរ'].map(asProcessedToken),
|
||||
caretSplitsToken: true
|
||||
};
|
||||
|
||||
|
|
@ -315,7 +438,7 @@ describe('Tokenization functions', function() {
|
|||
return wordBreakers.default(text, customization);
|
||||
}
|
||||
|
||||
it('treats caret as `eot` for pre-caret text', function() {
|
||||
it('treats caret as `eot` for pre-caret text tokenization', function() {
|
||||
let context = {
|
||||
left: "don-", // We use a hyphen here b/c single-quote is hardcoded.
|
||||
right: " worry",
|
||||
|
|
@ -326,21 +449,21 @@ describe('Tokenization functions', function() {
|
|||
let tokenization = models.tokenize(wordBreakers.default, context);
|
||||
|
||||
assert.deepEqual(tokenization, {
|
||||
left: ["don", "-"],
|
||||
right: ["worry"],
|
||||
left: ["don", "-"].map(asProcessedToken),
|
||||
right: [" ", "worry"].map(asProcessedToken),
|
||||
caretSplitsToken: false
|
||||
});
|
||||
|
||||
tokenization = models.tokenize(midLetterNonbreaker, context);
|
||||
|
||||
assert.deepEqual(tokenization, {
|
||||
left: ["don-"],
|
||||
right: ["worry"],
|
||||
left: ["don-"].map(asProcessedToken),
|
||||
right: [" ", "worry"].map(asProcessedToken),
|
||||
caretSplitsToken: false
|
||||
});
|
||||
});
|
||||
|
||||
it('handles mid-contraction tokenization', function() {
|
||||
it('handles mid-contraction tokenization (via wordbreaker customization)', function() {
|
||||
let context = {
|
||||
left: "don:",
|
||||
right: "t worry",
|
||||
|
|
@ -351,24 +474,108 @@ describe('Tokenization functions', function() {
|
|||
let tokenization = models.tokenize(wordBreakers.default, context);
|
||||
|
||||
assert.deepEqual(tokenization, {
|
||||
left: ["don", ":"], // This particular case feels like a possible issue.
|
||||
right: ["t", "worry"], // It'd be a three-way split token, as "don:t" would
|
||||
// be a single token were it not for the caret in the middle.
|
||||
// This particular case feels like a possible issue.
|
||||
// It'd be a three-way split token, as "don:t" would
|
||||
// be a single token were it not for the caret in the middle.
|
||||
left: ["don", ":"].map(asProcessedToken),
|
||||
right: ["t", " ", "worry"].map(asProcessedToken),
|
||||
|
||||
caretSplitsToken: false
|
||||
})
|
||||
|
||||
tokenization = models.tokenize(midLetterNonbreaker, context);
|
||||
|
||||
assert.deepEqual(tokenization, {
|
||||
left: ["don:"],
|
||||
right: ["t", "worry"],
|
||||
left: ["don:"].map(asProcessedToken),
|
||||
right: ["t", " ", "worry"].map(asProcessedToken),
|
||||
caretSplitsToken: true
|
||||
});
|
||||
});
|
||||
|
||||
it('mitigates effects of previously-distributed malformed wordbreaker output', function () {
|
||||
const text = 'the quick brown fox jumped over the lazy dog ';
|
||||
/** @type { Context } */
|
||||
const context = {
|
||||
left: text,
|
||||
right: '',
|
||||
startOfBuffer: true,
|
||||
endOfBuffer: true
|
||||
}
|
||||
|
||||
const tokenized = models.tokenize(customWordBreakerFormer, context);
|
||||
|
||||
// Mitigation aims to prevent the _worst_ side-effects that can result from invalidating the
|
||||
// underlying assumption of a monotonically-increasing index within the context -
|
||||
// assigning repeated or blank entries the text that preceded them!
|
||||
assert.notExists(tokenized.left.find((token) => token.text == text));
|
||||
assert.notExists(tokenized.left.find((token) => token.text.startsWith(text.substring(0, 25))));
|
||||
|
||||
// 'the' appears twice in the context, which should result in two separate 'the' tokens here.
|
||||
// This was improperly handled when we didn't check that assumption.
|
||||
assert.equal(tokenized.left.filter((token) => token.text == 'the').length, 2);
|
||||
|
||||
// Does not address multiple blank-token ('') entries that result from intervening spaces;
|
||||
// that would add too much extra complexity to the method... and it can already be
|
||||
// handled decently by the predictive-text engine.
|
||||
assert.deepEqual(
|
||||
tokenized.left
|
||||
.filter((entry) => !entry.isWhitespace)
|
||||
.filter((entry) => entry.text != '')
|
||||
.map((entry) => entry.text),
|
||||
['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog']
|
||||
);
|
||||
});
|
||||
|
||||
it('properly works with well-formed custom wordbreaker output', function () {
|
||||
const text = 'the quick brown fox jumped over the lazy dog ';
|
||||
/** @type { Context } */
|
||||
const context = {
|
||||
left: text,
|
||||
right: '',
|
||||
startOfBuffer: true,
|
||||
endOfBuffer: true
|
||||
}
|
||||
|
||||
const tokenized = models.tokenize(customWordBreakerProper, context);
|
||||
|
||||
// Easier-to-parse version
|
||||
assert.deepEqual(
|
||||
tokenized.left
|
||||
.filter((entry) => !entry.isWhitespace)
|
||||
.map((entry) => entry.text),
|
||||
['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog']
|
||||
);
|
||||
|
||||
// This time, with whitespaces.
|
||||
assert.deepEqual(
|
||||
tokenized.left.map((entry) => entry.text), [
|
||||
'the',
|
||||
' ',
|
||||
'quick',
|
||||
' ',
|
||||
'brown',
|
||||
' ',
|
||||
'fox',
|
||||
' ',
|
||||
'jumped',
|
||||
' ',
|
||||
'over',
|
||||
' ',
|
||||
'the',
|
||||
' ',
|
||||
'lazy',
|
||||
' ',
|
||||
'dog',
|
||||
' '
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
//
|
||||
});
|
||||
|
||||
describe('getLastPreCaretToken', function() {
|
||||
it('with pre-whitespace caret', function() {
|
||||
it('operates properly with pre-whitespace caret', function() {
|
||||
let context = {
|
||||
left: "The quick brown fox",
|
||||
right: " jumped over the lazy dog",
|
||||
|
|
@ -381,7 +588,7 @@ describe('Tokenization functions', function() {
|
|||
assert.equal(tokenization, 'fox');
|
||||
});
|
||||
|
||||
it('with post-whitespace caret', function() {
|
||||
it('operates properly with post-whitespace caret', function() {
|
||||
let context = {
|
||||
left: "The quick brown fox ",
|
||||
right: "jumped over the lazy dog",
|
||||
|
|
@ -394,7 +601,21 @@ describe('Tokenization functions', function() {
|
|||
assert.equal(tokenization, '');
|
||||
});
|
||||
|
||||
it('within a token', function() {
|
||||
|
||||
it('operates properly with post-whitespace caret, ascii breaker', function() {
|
||||
let context = {
|
||||
left: "The quick brown fox ",
|
||||
right: "jumped over the lazy dog",
|
||||
startOfBuffer: true,
|
||||
endOfBuffer: true
|
||||
};
|
||||
|
||||
let tokenization = models.getLastPreCaretToken(wordBreakers.ascii, context);
|
||||
|
||||
assert.equal(tokenization, '');
|
||||
});
|
||||
|
||||
it('operates properly within a token', function() {
|
||||
let context = {
|
||||
left: "The quick brown fox jum",
|
||||
right: "ped over the lazy dog",
|
||||
|
|
@ -406,10 +627,15 @@ describe('Tokenization functions', function() {
|
|||
|
||||
assert.equal(tokenization, 'jum');
|
||||
});
|
||||
|
||||
it('operates properly with no context', function() {
|
||||
let tokenization = models.getLastPreCaretToken(wordBreakers.default, null);
|
||||
assert.equal(tokenization, '');
|
||||
});
|
||||
});
|
||||
|
||||
describe('wordbreak', function() {
|
||||
it('with pre-whitespace caret', function() {
|
||||
it('operates properly with pre-whitespace caret', function() {
|
||||
let context = {
|
||||
left: "The quick brown fox",
|
||||
right: " jumped over the lazy dog",
|
||||
|
|
@ -422,7 +648,7 @@ describe('Tokenization functions', function() {
|
|||
assert.equal(tokenization, 'fox');
|
||||
});
|
||||
|
||||
it('with post-whitespace caret', function() {
|
||||
it('operates properly with post-whitespace caret', function() {
|
||||
let context = {
|
||||
left: "The quick brown fox ",
|
||||
right: "jumped over the lazy dog",
|
||||
|
|
@ -437,7 +663,7 @@ describe('Tokenization functions', function() {
|
|||
|
||||
// This version is subject to change. In the future, we may wish the wordbreak
|
||||
// operation to include "the rest of the word" - the post-caret part.
|
||||
it('within a token', function() {
|
||||
it('operates properly within a token', function() {
|
||||
let context = {
|
||||
left: "The quick brown fox jum",
|
||||
right: "ped over the lazy dog",
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
The presence of this file tells CI to use the new builder_ style parameters for build.sh and unit_tests/test.sh.
|
||||
|
|
@ -10,7 +10,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
|
||||
#
|
||||
# TODO: future modules may include
|
||||
# :lm-message-types \
|
||||
# :sentry-manager \
|
||||
#
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
# Keyman: Keyboard Processor Module
|
||||
The Original Code is (C) 2017-2020 SIL International
|
||||
|
||||
The Keyboard Processor module is an internal component of KeymanWeb, seen within this repo at /web/.
|
||||
|
||||
## Minimum Compilation Requirements
|
||||
|
||||
* A local installation of [Node.js](https://nodejs.org/) v8.9+.
|
||||
* Builds will call `npm install` to automatically install further necessary build dependencies.
|
||||
|
||||
* Linux users can update to LTS version of nodejs by following instructions on the [NodeSource Distributions](https://github.com/nodesource/distributions#table-of-contents) page.
|
||||
|
||||
**********************************************************************
|
||||
|
||||
The build script may be found at src/build.sh.
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Compile KeymanWeb's 'keyboard-processor' module, one of the components of Web's 'core' module.
|
||||
#
|
||||
## 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"
|
||||
|
||||
BUNDLE_CMD="node $KEYMAN_ROOT/common/web/es-bundling/build/common-bundle.mjs"
|
||||
|
||||
################################ Main script ################################
|
||||
|
||||
builder_describe "Builds the standalone, headless form of Keyman Engine for Web's input-processor module" \
|
||||
"@/common/web/keyman-version" \
|
||||
"@/common/web/keyboard-processor" \
|
||||
"@/common/predictive-text" \
|
||||
"@/developer/src/kmc-model test" \
|
||||
"clean" \
|
||||
"configure" \
|
||||
"build" \
|
||||
"test" \
|
||||
"--ci Sets $(builder_term test) action to use CI-based test configurations & reporting"
|
||||
|
||||
builder_describe_outputs \
|
||||
configure /node_modules \
|
||||
build /common/web/input-processor/build/lib/index.mjs \
|
||||
|
||||
builder_parse "$@"
|
||||
|
||||
function do_build() {
|
||||
tsc -b ./tsconfig.json
|
||||
|
||||
$BUNDLE_CMD "${KEYMAN_ROOT}/common/web/input-processor/build/obj/index.js" \
|
||||
--out "${KEYMAN_ROOT}/common/web/input-processor/build/lib/index.mjs" \
|
||||
--format esm
|
||||
|
||||
# Declaration bundling.
|
||||
tsc --emitDeclarationOnly --outFile ./build/lib/index.d.ts
|
||||
}
|
||||
|
||||
function do_test() {
|
||||
local FLAGS=
|
||||
if builder_has_option --ci; then
|
||||
FLAGS="--reporter mocha-teamcity-reporter"
|
||||
fi
|
||||
|
||||
mocha --recursive $FLAGS ./tests/cases/
|
||||
}
|
||||
|
||||
builder_run_action configure verify_npm_setup
|
||||
builder_run_action clean rm -rf build/
|
||||
builder_run_action build do_build
|
||||
builder_run_action test do_test
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
{
|
||||
"name": "@keymanapp/input-processor",
|
||||
"description": "The core text and prediction processing engine for KeymanWeb",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/keymanapp/keyman.git"
|
||||
},
|
||||
"keywords": [
|
||||
"input",
|
||||
"languages",
|
||||
"keyboards"
|
||||
],
|
||||
"author": "SIL International",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/keymanapp/keyman/issues"
|
||||
},
|
||||
"homepage": "https://github.com/keymanapp/keyman#readme",
|
||||
"devDependencies": {
|
||||
"@keymanapp/kmc": "*",
|
||||
"@keymanapp/resources-gosh": "*",
|
||||
"mocha": "^10.0.0",
|
||||
"mocha-teamcity-reporter": "^4.0.0",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "gosh ./test.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@keymanapp/keyboard-processor": "*",
|
||||
"@keymanapp/lexical-model-layer": "*",
|
||||
"@keymanapp/models-types": "*",
|
||||
"@keymanapp/keyman-version": "*",
|
||||
"@keymanapp/web-utils": "*",
|
||||
"eventemitter3": "^5.0.0"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "./build/obj/index.js",
|
||||
"types": "./build/obj/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"es6-bundling": "./src/index.ts",
|
||||
"default": "./build/obj/index.js"
|
||||
},
|
||||
"./lib": {
|
||||
"types": "./build/lib/index.d.ts",
|
||||
"import": "./build/lib/index.mjs"
|
||||
},
|
||||
"./obj/*.js": "./build/obj/*.js"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
/// <reference path="../../../predictive-text/src/worker-interface.d.ts" />
|
||||
|
||||
export * from './corrections.js';
|
||||
export * from './correctionLayout.js';
|
||||
export { default as InputProcessor } from './text/inputProcessor.js';
|
||||
export { default as ContextWindow } from './text/contextWindow.js';
|
||||
export { default as ModelSpec } from './text/prediction/modelSpec.js';
|
||||
export { default as LanguageProcessor, StateChangeEnum } from './text/prediction/languageProcessor.js';
|
||||
export { default as PredictionContext } from './text/prediction/predictionContext.js';
|
||||
export { TranscriptionCache } from './transcriptionCache.js';
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"extends": "../tsconfig.kmw-main-base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
// Necessary for ES5 iteration of Map.keys().
|
||||
"downlevelIteration": true,
|
||||
"outDir": "build/obj/",
|
||||
"tsBuildInfoFile": "build/obj/tsconfig.tsbuildinfo",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"references": [
|
||||
{ "path": "../utils" },
|
||||
{ "path": "../keyboard-processor" },
|
||||
{ "path": "../../predictive-text/tsconfig.all.json" },
|
||||
],
|
||||
// The extra .d.ts is necessary to avoid issues with lack of a Worker type definition when not including
|
||||
// the standard DOM in the TS "lib" configuration.
|
||||
"include": ["./src/**/*.ts", "../../predictive-text/src/worker-interface.d.ts"]
|
||||
}
|
||||
18
common/web/keyboard-processor/.gitignore
vendored
|
|
@ -1,18 +0,0 @@
|
|||
# Build products
|
||||
src/environment.inc.ts
|
||||
|
||||
# Legacy build folders.
|
||||
output/
|
||||
build/
|
||||
embedded/
|
||||
|
||||
# Current build output folder (matches standard node publish location).
|
||||
dist/
|
||||
|
||||
# Other local files.
|
||||
node_modules/
|
||||
unit_tests/modernizr.js
|
||||
source/environment.inc.ts
|
||||
.idea/**/*.xml
|
||||
*.iml
|
||||
*.kpj.user
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Compile KeymanWeb's 'keyboard-processor' module, one of the components of Web's 'core' module.
|
||||
#
|
||||
## 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"
|
||||
|
||||
BUNDLE_CMD="node ${KEYMAN_ROOT}/common/web/es-bundling/build/common-bundle.mjs"
|
||||
|
||||
################################ Main script ################################
|
||||
|
||||
builder_describe \
|
||||
"Compiles the web-oriented utility function module." \
|
||||
"@/common/web/recorder test" \
|
||||
"@/common/web/keyman-version" \
|
||||
"@/common/web/es-bundling" \
|
||||
"@/common/web/types" \
|
||||
"@/common/web/utils" \
|
||||
configure \
|
||||
clean \
|
||||
build \
|
||||
test \
|
||||
"--ci For use with action $(builder_term test) - emits CI-friendly test reports"
|
||||
|
||||
builder_describe_outputs \
|
||||
configure /node_modules \
|
||||
build /common/web/keyboard-processor/build/lib/index.mjs
|
||||
|
||||
builder_parse "$@"
|
||||
|
||||
function do_configure() {
|
||||
verify_npm_setup
|
||||
|
||||
# Configure Web browser-engine testing environments. As is, this should only
|
||||
# make changes when we update the dependency, even on our CI build agents.
|
||||
playwright install
|
||||
}
|
||||
|
||||
function do_build() {
|
||||
tsc --build "${THIS_SCRIPT_PATH}/tsconfig.all.json"
|
||||
|
||||
# Base product - the main keyboard processor
|
||||
builder_echo "Bundle base product - the main keyboard processor"
|
||||
${BUNDLE_CMD} "${KEYMAN_ROOT}/common/web/keyboard-processor/build/obj/index.js" \
|
||||
--out "${KEYMAN_ROOT}/common/web/keyboard-processor/build/lib/index.mjs" \
|
||||
--format esm
|
||||
|
||||
# The DOM-oriented keyboard loader
|
||||
builder_echo "Bundle the DOM-oriented keyboard loader"
|
||||
${BUNDLE_CMD} "${KEYMAN_ROOT}/common/web/keyboard-processor/build/obj/keyboards/loaders/dom-keyboard-loader.js" \
|
||||
--out "${KEYMAN_ROOT}/common/web/keyboard-processor/build/lib/dom-keyboard-loader.mjs" \
|
||||
--format esm
|
||||
|
||||
# The Node-oriented keyboard loader
|
||||
builder_echo "Bundle the Node-oriented keyboard loader"
|
||||
${BUNDLE_CMD} "${KEYMAN_ROOT}/common/web/keyboard-processor/build/obj/keyboards/loaders/node-keyboard-loader.js" \
|
||||
--out "${KEYMAN_ROOT}/common/web/keyboard-processor/build/lib/node-keyboard-loader.mjs" \
|
||||
--format esm \
|
||||
--platform node
|
||||
|
||||
# Tests
|
||||
builder_echo "Bundle tests"
|
||||
${BUNDLE_CMD} "${KEYMAN_ROOT}/common/web/keyboard-processor/build/tests/dom/cases/domKeyboardLoader.spec.js" \
|
||||
--out "${KEYMAN_ROOT}/common/web/keyboard-processor/build/tests/dom/domKeyboardLoader.spec.mjs" \
|
||||
--format esm
|
||||
|
||||
# Declaration bundling.
|
||||
builder_echo "Declaration bundling"
|
||||
tsc --emitDeclarationOnly --outFile ./build/lib/index.d.ts
|
||||
tsc --emitDeclarationOnly --outFile ./build/lib/dom-keyboard-loader.d.ts -p src/keyboards/loaders/tsconfig.dom.json
|
||||
tsc --emitDeclarationOnly --outFile ./build/lib/node-keyboard-loader.d.ts -p src/keyboards/loaders/tsconfig.node.json
|
||||
}
|
||||
|
||||
function do_test() {
|
||||
local MOCHA_FLAGS=
|
||||
local WTR_CONFIG=
|
||||
if builder_has_option --ci; then
|
||||
echo "Replacing user-friendly test reports with CI-friendly versions."
|
||||
MOCHA_FLAGS="$MOCHA_FLAGS --reporter mocha-teamcity-reporter"
|
||||
WTR_CONFIG=.CI
|
||||
fi
|
||||
|
||||
c8 mocha --recursive $MOCHA_FLAGS ./tests/node/
|
||||
web-test-runner --config tests/dom/web-test-runner${WTR_CONFIG}.config.mjs
|
||||
}
|
||||
|
||||
builder_run_action configure do_configure
|
||||
builder_run_action clean rm -rf ./build
|
||||
builder_run_action build do_build
|
||||
builder_run_action test do_test
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
{
|
||||
"name": "@keymanapp/keyboard-processor",
|
||||
"description": "Core module for Keyman keyboard support in KeymanWeb.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/keymanapp/keyman.git"
|
||||
},
|
||||
"keywords": [
|
||||
"input",
|
||||
"languages",
|
||||
"keyboards"
|
||||
],
|
||||
"author": "SIL International",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/keymanapp/keyman/issues"
|
||||
},
|
||||
"homepage": "https://github.com/keymanapp/keyman#readme",
|
||||
"devDependencies": {
|
||||
"@keymanapp/resources-gosh": "*",
|
||||
"c8": "^7.12.0",
|
||||
"mocha": "^10.0.0",
|
||||
"mocha-teamcity-reporter": "^4.0.0",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gosh build.sh",
|
||||
"clean": "gosh build.sh clean",
|
||||
"test": "gosh build.sh test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@keymanapp/common-types": "*",
|
||||
"@keymanapp/models-types": "*",
|
||||
"@keymanapp/keyman-version": "*",
|
||||
"@keymanapp/web-utils": "*"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "./build/obj/index.js",
|
||||
"types": "./build/obj/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"es6-bundling": "./src/index.ts",
|
||||
"default": "./build/obj/index.js"
|
||||
},
|
||||
"./node-keyboard-loader": {
|
||||
"es6-bundling": "./src/keyboards/loaders/node-keyboard-loader.ts",
|
||||
"types": "./build/obj/keyboards/loaders/node-keyboard-loader.d.ts",
|
||||
"import": "./build/obj/keyboards/loaders/node-keyboard-loader.js"
|
||||
},
|
||||
"./dom-keyboard-loader": {
|
||||
"es6-bundling": "./src/keyboards/loaders/dom-keyboard-loader.ts",
|
||||
"types": "./build/obj/keyboards/loaders/dom-keyboard-loader.d.ts",
|
||||
"import": "./build/obj/keyboards/loaders/dom-keyboard-loader.js"
|
||||
},
|
||||
"./lib": {
|
||||
"types": "./build/lib/index.d.ts",
|
||||
"import": "./build/lib/index.mjs"
|
||||
},
|
||||
"./lib/node-keyboard-loader": {
|
||||
"types": "./build/lib/node-keyboard-loader.d.ts",
|
||||
"import": "./build/lib/node-keyboard-loader.mjs"
|
||||
},
|
||||
"./lib/dom-keyboard-loader": {
|
||||
"types": "./build/lib/dom-keyboard-loader.d.ts",
|
||||
"import": "./build/lib/dom-keyboard-loader.mjs"
|
||||
},
|
||||
"./obj/*.js": "./build/obj/*.js"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.kmw-main-base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "../../../",
|
||||
"outDir": "../../../build/obj/keyboards/loaders/",
|
||||
"tsBuildInfoFile": "../../../build/obj/keyboards/loaders/tsconfig.dom.tsbuildinfo",
|
||||
"rootDir": "."
|
||||
},
|
||||
"references": [
|
||||
{ "path": "../../../tsconfig.json" }
|
||||
],
|
||||
"include": ["dom-keyboard-loader.ts", "domKeyboardLoader.ts"],
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.kmw-main-base.json",
|
||||
"compilerOptions": {
|
||||
"types": [ "node" ],
|
||||
"baseUrl": "../../../",
|
||||
"outDir": "../../../build/obj/keyboards/loaders/",
|
||||
"tsBuildInfoFile": "../../../build/obj/keyboards/loaders/tsconfig.node.tsbuildinfo",
|
||||
"rootDir": "."
|
||||
},
|
||||
"references": [
|
||||
{ "path": "../../../tsconfig.json" }
|
||||
],
|
||||
"include": ["node-keyboard-loader.ts", "nodeKeyboardLoader.ts"],
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
Automated tests in this subfolder and its children are designed to facilitate simple, browser-independent
|
||||
unit tests that are DOM-reliant.
|
||||
|
||||
Tests for anything that may reasonably vary depending upon the browser used to run the code should go under
|
||||
the "integrated" folder instead.
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
// @ts-check
|
||||
import { devices, playwrightLauncher } from '@web/test-runner-playwright';
|
||||
import { esbuildPlugin } from '@web/dev-server-esbuild';
|
||||
import { defaultReporter, summaryReporter } from '@web/test-runner';
|
||||
import { LauncherWrapper, sessionStabilityReporter } from '@keymanapp/common-test-resources/test-runner-stability-reporter.mjs';
|
||||
import { importMapsPlugin } from '@web/dev-server-import-maps';
|
||||
import { dirname, resolve } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const dir = dirname(fileURLToPath(import.meta.url));
|
||||
const KEYMAN_ROOT = resolve(dir, '../../../../..');
|
||||
|
||||
/** @type {import('@web/test-runner').TestRunnerConfig} */
|
||||
export default {
|
||||
// debug: true,
|
||||
browsers: [
|
||||
new LauncherWrapper(playwrightLauncher({ product: 'chromium' })),
|
||||
new LauncherWrapper(playwrightLauncher({ product: 'firefox' })),
|
||||
new LauncherWrapper(playwrightLauncher({ product: 'webkit', concurrency: 1 })),
|
||||
],
|
||||
concurrency: 10,
|
||||
nodeResolve: true,
|
||||
files: [
|
||||
'build/tests/dom/**/*.spec.mjs'
|
||||
],
|
||||
middleware: [
|
||||
// Rewrites short-hand paths for test resources, making them fully relative to the repo root.
|
||||
function rewriteResourcePath(context, next) {
|
||||
if(context.url.startsWith('/resources/')) {
|
||||
context.url = '/common/test' + context.url;
|
||||
}
|
||||
|
||||
return next();
|
||||
}
|
||||
],
|
||||
plugins: [
|
||||
esbuildPlugin({ts: true, target: 'auto'}),
|
||||
importMapsPlugin({
|
||||
inject: {
|
||||
importMap: {
|
||||
// Redirects `eventemitter3` imports to the bundled ESM library. The standard import is an
|
||||
// ESM wrapper around the CommonJS implementation, and WTR fails when it hits the CommonJS.
|
||||
imports: {
|
||||
'eventemitter3': '/node_modules/eventemitter3/dist/eventemitter3.esm.js'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
],
|
||||
reporters: [
|
||||
summaryReporter({}), /* local-dev mocha-style */
|
||||
sessionStabilityReporter({}),
|
||||
defaultReporter({})
|
||||
],
|
||||
/*
|
||||
Un-comment the next two lines for easy interactive debugging; it'll launch the
|
||||
test page in your preferred browser.
|
||||
*/
|
||||
// open: true,
|
||||
// manual: true,
|
||||
rootDir: KEYMAN_ROOT
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "../",
|
||||
"outDir": "../build/tests/",
|
||||
"tsBuildInfoFile": "../build/tests/tsconfig.tsbuildinfo",
|
||||
"rootDir": "./"
|
||||
},
|
||||
"include": [ "./dom/**/*.ts"],
|
||||
"exclude": []
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
// Is used by the keyboard-loader submodules.
|
||||
{
|
||||
"extends": "../tsconfig.kmw-main-base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "build/obj/",
|
||||
"tsBuildInfoFile": "build/obj/tsconfig.tsbuildinfo",
|
||||
"rootDir": "./src/"
|
||||
},
|
||||
"references": [
|
||||
{ "path": "../types" },
|
||||
{ "path": "../../models/types" },
|
||||
{ "path": "../keyman-version/" },
|
||||
{ "path": "../utils/" }
|
||||
],
|
||||
"include": [ "./src/**/*.ts"],
|
||||
"exclude": ["./src/keyboards/loaders/**/*.ts"]
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"outDir": "build/",
|
||||
"sourceMap": true,
|
||||
"lib": ["es6"],
|
||||
"target": "es6"
|
||||
},
|
||||
"include": ["./*.ts"],
|
||||
"exclude": ["test.ts"]
|
||||
}
|
||||
|
|
@ -1,597 +0,0 @@
|
|||
import { applyTransform, tokenize } from '@keymanapp/models-templates';
|
||||
import { defaultWordbreaker } from '@keymanapp/models-wordbreakers';
|
||||
|
||||
import { ClassicalDistanceCalculation } from './classical-calculation.js';
|
||||
import { SearchSpace } from './distance-modeler.js';
|
||||
import TransformUtils from '../transformUtils.js';
|
||||
|
||||
function textToCharTransforms(text: string, transformId?: number) {
|
||||
let perCharTransforms: Transform[] = [];
|
||||
|
||||
for(let i=0; i < text.kmwLength(); i++) {
|
||||
let char = text.kmwCharAt(i); // is SMP-aware
|
||||
|
||||
let transform: Transform = {
|
||||
insert: char,
|
||||
deleteLeft: 0,
|
||||
id: transformId
|
||||
};
|
||||
|
||||
perCharTransforms.push(transform);
|
||||
}
|
||||
|
||||
return perCharTransforms;
|
||||
}
|
||||
|
||||
export class TrackedContextSuggestion {
|
||||
suggestion: Suggestion;
|
||||
tokenWidth: number;
|
||||
}
|
||||
|
||||
export class TrackedContextToken {
|
||||
raw: string;
|
||||
replacementText: string;
|
||||
|
||||
transformDistributions: Distribution<Transform>[] = [];
|
||||
replacements: TrackedContextSuggestion[];
|
||||
activeReplacementId: number = -1;
|
||||
|
||||
get currentText(): string {
|
||||
if(this.replacementText === undefined || this.replacementText === null) {
|
||||
return this.raw;
|
||||
} else {
|
||||
return this.replacementText;
|
||||
}
|
||||
}
|
||||
|
||||
get replacement(): TrackedContextSuggestion {
|
||||
let replacementId = this.activeReplacementId;
|
||||
return this.replacements.find(function(replacement) {
|
||||
return replacement.suggestion.id == replacementId;
|
||||
});
|
||||
}
|
||||
|
||||
revert() {
|
||||
delete this.activeReplacementId;
|
||||
}
|
||||
}
|
||||
|
||||
export class TrackedContextState {
|
||||
// Stores the post-transform Context. Useful as a debugging reference, but also used to
|
||||
// pre-validate context state matches in case of discarded changes from multitaps.
|
||||
taggedContext: Context;
|
||||
model: LexicalModel;
|
||||
|
||||
tokens: TrackedContextToken[];
|
||||
/**
|
||||
* How many tokens were removed from the start of the best-matching ancestor.
|
||||
* Useful for restoring older states, e.g., when the user moves the caret backwards, we can recover the context at that position.
|
||||
*/
|
||||
indexOffset: number;
|
||||
|
||||
// Tracks all search spaces starting at the current token.
|
||||
// In the lm-layer's current form, this should only ever have one entry.
|
||||
// Leaves 'design space' for if/when we add support for phrase-level corrections/predictions.
|
||||
searchSpace: SearchSpace[] = [];
|
||||
|
||||
constructor(source: TrackedContextState);
|
||||
constructor(model: LexicalModel);
|
||||
constructor(obj: TrackedContextState | LexicalModel) {
|
||||
if(obj instanceof TrackedContextState) {
|
||||
let source = obj;
|
||||
// Be sure to deep-copy the tokens! Pointer-aliasing is bad here.
|
||||
this.tokens = source.tokens.map(function(token) {
|
||||
let copy = new TrackedContextToken();
|
||||
copy.raw = token.raw;
|
||||
copy.replacements = [].concat(token.replacements);
|
||||
copy.activeReplacementId = token.activeReplacementId;
|
||||
copy.transformDistributions = [].concat(token.transformDistributions);
|
||||
|
||||
if(token.replacementText) {
|
||||
copy.replacementText = token.replacementText;
|
||||
}
|
||||
|
||||
return copy;
|
||||
});
|
||||
|
||||
this.indexOffset = 0;
|
||||
const lexicalModel = this.model = obj.model;
|
||||
this.taggedContext = obj.taggedContext;
|
||||
|
||||
if(lexicalModel?.traverseFromRoot) {
|
||||
// We need to construct a separate search space from other ContextStates.
|
||||
//
|
||||
// In case we are unable to perfectly track context (say, due to multitaps)
|
||||
// we need to ensure that only fully-utilized keystrokes are considered.
|
||||
this.searchSpace = obj.searchSpace.map((space) => new SearchSpace(space));
|
||||
}
|
||||
} else {
|
||||
let lexicalModel = obj;
|
||||
this.tokens = [];
|
||||
this.indexOffset = Number.MIN_SAFE_INTEGER;
|
||||
this.model = lexicalModel;
|
||||
|
||||
if(lexicalModel && lexicalModel.traverseFromRoot) {
|
||||
this.searchSpace = [new SearchSpace(lexicalModel)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get head(): TrackedContextToken {
|
||||
return this.tokens[0];
|
||||
}
|
||||
|
||||
get tail(): TrackedContextToken {
|
||||
return this.tokens[this.tokens.length - 1];
|
||||
}
|
||||
|
||||
popHead() {
|
||||
this.tokens.splice(0, 2);
|
||||
this.indexOffset -= 1;
|
||||
}
|
||||
|
||||
pushTail(token: TrackedContextToken) {
|
||||
if(this.model && this.model.traverseFromRoot) {
|
||||
this.searchSpace = [new SearchSpace(this.model)]; // yeah, need to update SearchSpace for compatibility
|
||||
} else {
|
||||
this.searchSpace = [];
|
||||
}
|
||||
this.tokens.push(token);
|
||||
|
||||
let state = this;
|
||||
if(state.searchSpace.length > 0) {
|
||||
token.transformDistributions.forEach(distrib => state.searchSpace[0].addInput(distrib));
|
||||
}
|
||||
}
|
||||
|
||||
pushWhitespaceToTail(transformDistribution: Distribution<Transform> = null) {
|
||||
let whitespaceToken = new TrackedContextToken();
|
||||
|
||||
// Track the Transform that resulted in the whitespace 'token'.
|
||||
// Will be needed for phrase-level correction/prediction.
|
||||
whitespaceToken.transformDistributions = transformDistribution ? [transformDistribution] : [];
|
||||
|
||||
whitespaceToken.raw = null;
|
||||
this.tokens.push(whitespaceToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for 14.0's backspace workaround, which flattens all previous Distribution<Transform>
|
||||
* entries because of limitations with direct use of backspace transforms.
|
||||
* @param tokenText
|
||||
* @param transformId
|
||||
*/
|
||||
replaceTailForBackspace(tokenText: USVString, transformId: number) {
|
||||
this.tokens.pop();
|
||||
|
||||
// It's a backspace transform; time for special handling!
|
||||
//
|
||||
// For now, with 14.0, we simply compress all remaining Transforms for the token into
|
||||
// multiple single-char transforms. Probabalistically modeling BKSP is quite complex,
|
||||
// so we simplify by assuming everything remaining after a BKSP is 'true' and 'intended' text.
|
||||
//
|
||||
// Note that we cannot just use a single, monolithic transform at this point b/c
|
||||
// of our current edit-distance optimization strategy; diagonalization is currently...
|
||||
// not very compatible with that.
|
||||
let backspacedTokenContext: Distribution<Transform>[] = textToCharTransforms(tokenText, transformId).map(function(transform) {
|
||||
return [{sample: transform, p: 1.0}];
|
||||
});
|
||||
|
||||
let compactedToken = new TrackedContextToken();
|
||||
compactedToken.raw = tokenText;
|
||||
compactedToken.transformDistributions = backspacedTokenContext;
|
||||
this.pushTail(compactedToken);
|
||||
}
|
||||
|
||||
updateTail(transformDistribution: Distribution<Transform>, tokenText?: USVString) {
|
||||
let editedToken = this.tail;
|
||||
|
||||
// Preserve existing text if new text isn't specified.
|
||||
tokenText = tokenText || (tokenText === '' ? '' : editedToken.raw);
|
||||
|
||||
if(transformDistribution && transformDistribution.length > 0) {
|
||||
editedToken.transformDistributions.push(transformDistribution);
|
||||
if(this.searchSpace) {
|
||||
this.searchSpace.forEach(space => space.addInput(transformDistribution));
|
||||
}
|
||||
}
|
||||
// Replace old token's raw-text with new token's raw-text.
|
||||
editedToken.raw = tokenText;
|
||||
}
|
||||
|
||||
toRawTokenization() {
|
||||
let sequence: USVString[] = [];
|
||||
|
||||
for(let token of this.tokens) {
|
||||
// Hide any tokens representing wordbreaks. (Thinking ahead to phrase-level possibilities)
|
||||
if(token.currentText !== null) {
|
||||
sequence.push(token.currentText);
|
||||
}
|
||||
}
|
||||
|
||||
return sequence;
|
||||
}
|
||||
}
|
||||
|
||||
class CircularArray<Item> {
|
||||
static readonly DEFAULT_ARRAY_SIZE = 5;
|
||||
private circle: Item[];
|
||||
private currentHead: number=0;
|
||||
private currentTail: number=0;
|
||||
|
||||
constructor(size: number = CircularArray.DEFAULT_ARRAY_SIZE) {
|
||||
this.circle = Array(size);
|
||||
}
|
||||
|
||||
get count(): number {
|
||||
let diff = this.currentHead - this.currentTail;
|
||||
|
||||
if(diff < 0) {
|
||||
diff = diff + this.circle.length;
|
||||
}
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
get maxCount(): number {
|
||||
return this.circle.length;
|
||||
}
|
||||
|
||||
get oldest(): Item {
|
||||
if(this.count == 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.item(0);
|
||||
}
|
||||
|
||||
get newest(): Item {
|
||||
if(this.count == 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.item(this.count - 1);
|
||||
}
|
||||
|
||||
enqueue(item: Item): Item {
|
||||
var prevItem = null;
|
||||
let nextHead = (this.currentHead + 1) % this.maxCount;
|
||||
|
||||
if(nextHead == this.currentTail) {
|
||||
prevItem = this.circle[this.currentTail];
|
||||
this.currentTail = (this.currentTail + 1) % this.maxCount;
|
||||
}
|
||||
|
||||
this.circle[this.currentHead] = item;
|
||||
this.currentHead = nextHead;
|
||||
|
||||
return prevItem;
|
||||
}
|
||||
|
||||
dequeue(): Item {
|
||||
if(this.currentTail == this.currentHead) {
|
||||
return null;
|
||||
} else {
|
||||
let item = this.circle[this.currentTail];
|
||||
this.currentTail = (this.currentTail + 1) % this.maxCount;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
popNewest(): Item {
|
||||
if(this.currentTail == this.currentHead) {
|
||||
return null;
|
||||
} else {
|
||||
let item = this.circle[this.currentHead];
|
||||
this.currentHead = (this.currentHead - 1 + this.maxCount) % this.maxCount;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns items contained within the circular array, ordered from 'oldest' to 'newest' -
|
||||
* the same order in which the items will be dequeued.
|
||||
* @param index
|
||||
*/
|
||||
item(index: number) {
|
||||
if(index >= this.count) {
|
||||
throw "Invalid array index";
|
||||
}
|
||||
|
||||
let mappedIndex = (this.currentTail + index) % this.maxCount;
|
||||
return this.circle[mappedIndex];
|
||||
}
|
||||
}
|
||||
|
||||
export class ContextTracker extends CircularArray<TrackedContextState> {
|
||||
static attemptMatchContext(tokenizedContext: USVString[],
|
||||
matchState: TrackedContextState,
|
||||
transformDistribution?: Distribution<Transform>): TrackedContextState {
|
||||
// Map the previous tokenized state to an edit-distance friendly version.
|
||||
let matchContext: USVString[] = matchState.toRawTokenization();
|
||||
|
||||
// Inverted order, since 'match' existed before our new context.
|
||||
let mapping = ClassicalDistanceCalculation.computeDistance(matchContext.map(value => ({key: value})),
|
||||
tokenizedContext.map(value => ({key: value})),
|
||||
1);
|
||||
|
||||
let editPath = mapping.editPath();
|
||||
|
||||
let poppedHead = false;
|
||||
let pushedTail = false;
|
||||
|
||||
// Matters greatly when starting from a nil context.
|
||||
if(editPath.length > 1) {
|
||||
// First entry: may not be an 'insert' or a 'transpose' op.
|
||||
// 'insert' allowed if the next token is 'substitute', as this may occur with an edit path of length 2.
|
||||
if((editPath[0] == 'insert' && !(editPath[1] == 'substitute' && editPath.length == 2)) || editPath[0].indexOf('transpose') >= 0) {
|
||||
return null;
|
||||
} else if(editPath[0] == 'delete') {
|
||||
poppedHead = true; // a token from the previous state has been wholly removed.
|
||||
}
|
||||
}
|
||||
|
||||
// Last entry: may not be a 'delete' or a 'transpose' op.
|
||||
let tailIndex = editPath.length -1;
|
||||
let ignorePenultimateMatch = false;
|
||||
if(editPath[tailIndex] == 'delete' || editPath[0].indexOf('transpose') >= 0) {
|
||||
return null;
|
||||
} else if(editPath[tailIndex] == 'insert') {
|
||||
pushedTail = true;
|
||||
} else if(tailIndex > 0 && editPath[tailIndex-1] == 'insert' && editPath[tailIndex] == 'substitute') {
|
||||
// Tends to happen when accepting suggestions.
|
||||
pushedTail = true;
|
||||
ignorePenultimateMatch = true;
|
||||
}
|
||||
|
||||
// Can happen for the first text input after backspace deletes a wordbreaking character,
|
||||
// thus the new input continues a previous word while dropping the empty word after
|
||||
// that prior wordbreaking character.
|
||||
//
|
||||
// We can't handle it reliably from this match state, but a previous entry (without the empty token)
|
||||
// should still be in the cache and will be reliable for this example case.
|
||||
if(tailIndex > 0 && editPath[tailIndex-1] == 'delete' && editPath[tailIndex] == 'substitute') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Now to check everything in-between: should be exclusively 'match'es.
|
||||
for(let index = 1; index < editPath.length - (ignorePenultimateMatch ? 2 : 1); index++) {
|
||||
if(editPath[index] != 'match') {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// If we've made it here... success! We have a context match!
|
||||
let state: TrackedContextState;
|
||||
|
||||
if(pushedTail) {
|
||||
// On suggestion acceptance, we should update the previous final token.
|
||||
// We do it first so that the acceptance is replicated in the new TrackedContextState
|
||||
// as well.
|
||||
if(ignorePenultimateMatch) {
|
||||
// For this case, we were likely called by ModelCompositor.acceptSuggestion(), which
|
||||
// would have marked the accepted suggestion.
|
||||
matchState.tail.replacementText = tokenizedContext[tokenizedContext.length-2];
|
||||
}
|
||||
|
||||
state = new TrackedContextState(matchState);
|
||||
} else {
|
||||
// We're continuing a previously-cached context; create a deep-copy of it.
|
||||
// We can't just re-use the old instance, unfortunately; predictions break
|
||||
// with multitaps otherwise - we should avoid tracking keystrokes that were
|
||||
// rewound.
|
||||
//
|
||||
// If there are no incoming transforms, though... yeah, re-use is safe then.
|
||||
state = !!transformDistribution ? new TrackedContextState(matchState) : matchState;
|
||||
}
|
||||
|
||||
const hasDistribution = transformDistribution && Array.isArray(transformDistribution);
|
||||
let primaryInput = hasDistribution ? transformDistribution[0].sample : null;
|
||||
if(primaryInput && primaryInput.insert == "" && primaryInput.deleteLeft == 0 && !primaryInput.deleteRight) {
|
||||
primaryInput = null;
|
||||
}
|
||||
|
||||
const isWhitespace = primaryInput && TransformUtils.isWhitespace(primaryInput);
|
||||
const isBackspace = primaryInput && TransformUtils.isBackspace(primaryInput);
|
||||
const finalToken = tokenizedContext[tokenizedContext.length-1];
|
||||
|
||||
/* Assumption: This is an adequate check for its two sub-branches.
|
||||
*
|
||||
* Basis:
|
||||
* - Assumption: one keystroke may only cause a single token to rotate out of context.
|
||||
* - That is, no "reasonable" keystroke would emit enough code points to 'bump' two words simultaneously.
|
||||
* - ... This one may need to be loosened a bit... but it should be enough for initial correction testing as-is.
|
||||
* - Assumption: one keystroke may only cause a single token to be appended to the context
|
||||
* - That is, no "reasonable" keystroke would emit a Transform adding two separate word tokens
|
||||
* - For languages using whitespace to word-break, said keystroke would have to include said whitespace to break the assumption.
|
||||
*/
|
||||
|
||||
function maintainLastToken() {
|
||||
if(isWhitespace && editPath[tailIndex] == 'match') {
|
||||
/*
|
||||
We can land here if there are multiple whitespaces in a row.
|
||||
There's already an implied whitespace to the left, so we conceptually
|
||||
merge the new whitespace with that one.
|
||||
*/
|
||||
return;
|
||||
} else if(isBackspace) {
|
||||
// Consider backspace entry for this case?
|
||||
state.replaceTailForBackspace(finalToken, primaryInput.id);
|
||||
} else {
|
||||
state.updateTail(primaryInput ? transformDistribution : null, finalToken);
|
||||
}
|
||||
}
|
||||
|
||||
// If there is/was more than one context token available...
|
||||
if(editPath.length > 1) {
|
||||
// We're removing a context token, but at least one remains.
|
||||
if(poppedHead) {
|
||||
state.popHead();
|
||||
}
|
||||
|
||||
// We're adding an additional context token.
|
||||
if(pushedTail) {
|
||||
const tokenizedTail = tokenizedContext[tokenizedContext.length - 1];
|
||||
/*
|
||||
* Common-case: most transforms that trigger this case are from pure-whitespace Transforms. MOST.
|
||||
*
|
||||
* Less-common, but noteworthy: some wordbreaks may occur without whitespace. Example:
|
||||
* `"o` => ['"', 'o']. Make sure to double-check against `tokenizedContext`!
|
||||
*/
|
||||
let pushedToken = new TrackedContextToken();
|
||||
pushedToken.raw = tokenizedTail;
|
||||
|
||||
if(isWhitespace || !primaryInput) {
|
||||
state.pushWhitespaceToTail(transformDistribution ?? []);
|
||||
// Continuing the earlier assumption, that 'pure-whitespace Transform' does not emit any initial characters
|
||||
// for the new word (token), so the input keystrokes do not correspond to the new text token.
|
||||
pushedToken.transformDistributions = [];
|
||||
} else {
|
||||
state.pushWhitespaceToTail();
|
||||
// Assumption: Since we only allow one-transform-at-a-time changes between states, we shouldn't be missing
|
||||
// any metadata used to construct the new context state token.
|
||||
pushedToken.transformDistributions = transformDistribution ? [transformDistribution] : [];
|
||||
}
|
||||
|
||||
state.pushTail(pushedToken);
|
||||
} else {
|
||||
// We're editing the final context token.
|
||||
// TODO: Assumption: we didn't 'miss' any inputs somehow.
|
||||
// As is, may be prone to fragility should the lm-layer's tracked context 'desync' from its host's.
|
||||
maintainLastToken();
|
||||
}
|
||||
// There is only one word in the context.
|
||||
} else {
|
||||
// TODO: Assumption: we didn't 'miss' any inputs somehow.
|
||||
// As is, may be prone to fragility should the lm-layer's tracked context 'desync' from its host's.
|
||||
|
||||
if(editPath[tailIndex] == 'insert') {
|
||||
// Construct appropriate initial token.
|
||||
let token = new TrackedContextToken();
|
||||
token.raw = tokenizedContext[0];
|
||||
token.transformDistributions = [transformDistribution];
|
||||
state.pushTail(token);
|
||||
} else {
|
||||
// Edit the lone context token.
|
||||
maintainLastToken();
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
private static modelContextState(tokenizedContext: USVString[],
|
||||
transformDistribution: Distribution<Transform>,
|
||||
lexicalModel: LexicalModel): TrackedContextState {
|
||||
let baseTokens = tokenizedContext.map(function(entry) {
|
||||
let token = new TrackedContextToken();
|
||||
token.raw = entry;
|
||||
if(token.raw) {
|
||||
token.transformDistributions = textToCharTransforms(token.raw).map(function(transform) {
|
||||
return [{sample: transform, p: 1.0}];
|
||||
});
|
||||
} else {
|
||||
// Helps model context-final wordbreaks.
|
||||
token.transformDistributions = [];
|
||||
}
|
||||
return token;
|
||||
});
|
||||
|
||||
// And now build the final context state object, which includes whitespace 'tokens'.
|
||||
let state = new TrackedContextState(lexicalModel);
|
||||
|
||||
if(baseTokens.length > 0) {
|
||||
state.pushTail(baseTokens.splice(0, 1)[0]);
|
||||
}
|
||||
|
||||
while(baseTokens.length > 0) {
|
||||
state.pushWhitespaceToTail();
|
||||
state.pushTail(baseTokens.splice(0, 1)[0]);
|
||||
}
|
||||
|
||||
if(state.tokens.length == 0) {
|
||||
let token = new TrackedContextToken();
|
||||
token.raw = '';
|
||||
|
||||
state.pushTail(token);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the current, post-input context against the most recently-seen contexts from previous prediction calls, returning
|
||||
* the most information-rich `TrackedContextState` possible. If a match is found, the state will be annotated with the
|
||||
* input information provided to previous prediction calls and persisted correction-search calculations for re-use.
|
||||
*
|
||||
* @param model
|
||||
* @param context
|
||||
* @param transformDistribution
|
||||
*/
|
||||
analyzeState(model: LexicalModel,
|
||||
context: Context,
|
||||
transformDistribution?: Distribution<Transform>): TrackedContextState {
|
||||
if(!model.traverseFromRoot) {
|
||||
// Assumption: LexicalModel provides a valid traverseFromRoot function. (Is technically optional)
|
||||
// Without it, no 'corrections' may be made; the model can only be used to predict, not correct.
|
||||
throw "This lexical model does not provide adequate data for correction algorithms and context reuse";
|
||||
}
|
||||
|
||||
let tokenizedContext = tokenize(model.wordbreaker || defaultWordbreaker, context);
|
||||
|
||||
if(tokenizedContext.left.length > 0) {
|
||||
for(let i = this.count - 1; i >= 0; i--) {
|
||||
const priorMatchState = this.item(i);
|
||||
const priorTaggedContext = priorMatchState.taggedContext;
|
||||
if(priorTaggedContext && transformDistribution && transformDistribution.length > 0) {
|
||||
// Using the potential `matchState` + the incoming transform, do the results line up for
|
||||
// our observed context? If not, skip it.
|
||||
//
|
||||
// Necessary to properly handle multitaps, as there are context rewinds that the
|
||||
// predictive-text engine is not otherwise warned about.
|
||||
//
|
||||
// `priorTaggedContext` must not be `null`!
|
||||
const doublecheckContext = applyTransform(transformDistribution[0].sample, priorTaggedContext);
|
||||
if(doublecheckContext.left != context.left) {
|
||||
continue;
|
||||
}
|
||||
} else if(priorTaggedContext?.left != context.left) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let resultState = ContextTracker.attemptMatchContext(tokenizedContext.left, this.item(i), transformDistribution);
|
||||
|
||||
if(resultState) {
|
||||
// Keep it reasonably current! And it's probably fine to have it more than once
|
||||
// in the history. However, if it's the most current already, there's no need
|
||||
// to refresh it.
|
||||
if(this.newest != resultState && this.newest != priorMatchState) {
|
||||
// Already has a taggedContext.
|
||||
this.enqueue(priorMatchState);
|
||||
}
|
||||
|
||||
resultState.taggedContext = context;
|
||||
if(resultState != this.item(i)) {
|
||||
this.enqueue(resultState);
|
||||
}
|
||||
return resultState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Else: either empty OR we've detected a 'new context'. Initialize from scratch; no prior input information is
|
||||
// available. Only the results of the prior inputs are known.
|
||||
//
|
||||
// Assumption: as a caret needs to move to context before any actual transform distributions occur,
|
||||
// this state is only reached on caret moves; thus, transformDistribution is actually just a single null transform.
|
||||
let state = ContextTracker.modelContextState(tokenizedContext.left, transformDistribution, model);
|
||||
state.taggedContext = context;
|
||||
this.enqueue(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
while(this.count > 0) {
|
||||
this.dequeue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
import { assert } from 'chai';
|
||||
|
||||
import { ContextTracker } from '#./correction/context-tracker.js';
|
||||
import ModelCompositor from '#./model-compositor.js';
|
||||
import * as models from '#./models/index.js';
|
||||
|
||||
import { jsonFixture } from '@keymanapp/common-test-resources/model-helpers.mjs';
|
||||
|
||||
describe('ContextTracker', function() {
|
||||
function toWrapperDistribution(transform) {
|
||||
return [{
|
||||
sample: transform,
|
||||
p: 1.0
|
||||
}];
|
||||
}
|
||||
|
||||
describe('attemptMatchContext', function() {
|
||||
it("properly matches and aligns when lead token is removed", function() {
|
||||
let existingContext = ["an", "apple", "a", "day", "keeps", "the", "doctor"];
|
||||
let transform = {
|
||||
insert: '',
|
||||
deleteLeft: 0
|
||||
}
|
||||
let newContext = existingContext.slice(0);
|
||||
newContext.splice(0, 1);
|
||||
let rawTokens = ["apple", null, "a", null, "day", null, "keeps", null, "the", null, "doctor"];
|
||||
|
||||
let existingState = ContextTracker.modelContextState(existingContext);
|
||||
let state = ContextTracker.attemptMatchContext(newContext, existingState, null, toWrapperDistribution(transform));
|
||||
assert.isNotNull(state);
|
||||
assert.deepEqual(state.tokens.map(token => token.raw), rawTokens);
|
||||
});
|
||||
|
||||
it("properly matches and aligns when final token is edited", function() {
|
||||
let existingContext = ["an", "apple", "a", "day", "keeps", "the", "docto"];
|
||||
let transform = {
|
||||
insert: 'r',
|
||||
deleteLeft: 0
|
||||
}
|
||||
let newContext = existingContext.slice(0);
|
||||
newContext[newContext.length - 1] = 'doctor';
|
||||
let rawTokens = ["an", null, "apple", null, "a", null, "day", null, "keeps", null, "the", null, "doctor"];
|
||||
|
||||
let existingState = ContextTracker.modelContextState(existingContext);
|
||||
let state = ContextTracker.attemptMatchContext(newContext, existingState, null, toWrapperDistribution(transform));
|
||||
assert.isNotNull(state);
|
||||
assert.deepEqual(state.tokens.map(token => token.raw), rawTokens);
|
||||
});
|
||||
|
||||
it("properly matches and aligns when a 'wordbreak' is added", function() {
|
||||
let existingContext = ["an", "apple", "a", "day", "keeps", "the", "doctor"];
|
||||
let transform = {
|
||||
insert: ' ',
|
||||
deleteLeft: 0
|
||||
}
|
||||
let newContext = existingContext.slice(0);
|
||||
newContext.push('');
|
||||
let rawTokens = ["an", null, "apple", null, "a", null, "day", null, "keeps", null, "the", null, "doctor", null, ""];
|
||||
|
||||
let existingState = ContextTracker.modelContextState(existingContext);
|
||||
let state = ContextTracker.attemptMatchContext(newContext, existingState, toWrapperDistribution(transform));
|
||||
assert.isNotNull(state);
|
||||
assert.deepEqual(state.tokens.map(token => token.raw), rawTokens);
|
||||
|
||||
// The 'wordbreak' transform
|
||||
assert.isNotEmpty(state.tokens[state.tokens.length - 2].transformDistributions);
|
||||
assert.isEmpty(state.tokens[state.tokens.length - 1].transformDistributions);
|
||||
});
|
||||
|
||||
it("properly matches and aligns when an implied 'wordbreak' occurs (as when following \"'\")", function() {
|
||||
let existingContext = ["'"];
|
||||
let transform = {
|
||||
insert: 'a',
|
||||
deleteLeft: 0
|
||||
}
|
||||
let newContext = existingContext.slice(0);
|
||||
newContext.push('a'); // The incoming transform should produce a new token WITH TEXT.
|
||||
let rawTokens = ["'", null, "a"];
|
||||
|
||||
let existingState = ContextTracker.modelContextState(existingContext);
|
||||
let state = ContextTracker.attemptMatchContext(newContext, existingState, toWrapperDistribution(transform));
|
||||
assert.isNotNull(state);
|
||||
assert.deepEqual(state.tokens.map(token => token.raw), rawTokens);
|
||||
|
||||
// The 'wordbreak' transform
|
||||
assert.isEmpty(state.tokens[state.tokens.length - 2].transformDistributions);
|
||||
assert.isNotEmpty(state.tokens[state.tokens.length - 1].transformDistributions);
|
||||
});
|
||||
|
||||
it("properly matches and aligns when lead token is removed AND a 'wordbreak' is added'", function() {
|
||||
let existingContext = ["an", "apple", "a", "day", "keeps", "the", "doctor"];
|
||||
let transform = {
|
||||
insert: ' ',
|
||||
deleteLeft: 0
|
||||
}
|
||||
let newContext = existingContext.slice(0);
|
||||
newContext.splice(0, 1);
|
||||
newContext.push('');
|
||||
let rawTokens = ["apple", null, "a", null, "day", null, "keeps", null, "the", null, "doctor", null, ""];
|
||||
|
||||
let existingState = ContextTracker.modelContextState(existingContext);
|
||||
let state = ContextTracker.attemptMatchContext(newContext, existingState, toWrapperDistribution(transform));
|
||||
assert.isNotNull(state);
|
||||
assert.deepEqual(state.tokens.map(token => token.raw), rawTokens);
|
||||
|
||||
// The 'wordbreak' transform
|
||||
assert.isNotEmpty(state.tokens[state.tokens.length - 2].transformDistributions);
|
||||
assert.isEmpty(state.tokens[state.tokens.length - 1].transformDistributions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('modelContextState', function() {
|
||||
it('models without final wordbreak', function() {
|
||||
let context = ["an", "apple", "a", "day", "keeps", "the", "doctor"];
|
||||
let rawTokens = ["an", null, "apple", null, "a", null, "day", null, "keeps", null, "the", null, "doctor"];
|
||||
|
||||
let state = ContextTracker.modelContextState(context);
|
||||
assert.deepEqual(state.tokens.map(token => token.raw), rawTokens);
|
||||
});
|
||||
|
||||
it('models with final wordbreak', function() {
|
||||
let context = ["an", "apple", "a", "day", "keeps", "the", "doctor", ""];
|
||||
let rawTokens = ["an", null, "apple", null, "a", null, "day", null, "keeps", null, "the", null, "doctor", null, ""];
|
||||
|
||||
let state = ContextTracker.modelContextState(context);
|
||||
assert.deepEqual(state.tokens.map(token => token.raw), rawTokens);
|
||||
});
|
||||
});
|
||||
|
||||
describe('suggestion acceptance tracking', function() {
|
||||
let englishPunctuation = {
|
||||
quotesForKeepSuggestion: { open: `“`, close: `”`},
|
||||
insertAfterWord: ' '
|
||||
};
|
||||
|
||||
it('tracks an accepted suggestion', function() {
|
||||
let baseSuggestion = {
|
||||
transform: {
|
||||
insert: 'world ',
|
||||
deleteLeft: 3,
|
||||
id: 0
|
||||
},
|
||||
transformId: 0,
|
||||
id: 1,
|
||||
displayAs: 'world'
|
||||
};
|
||||
|
||||
let baseContext = {
|
||||
left: 'hello wor', startOfBuffer: true, endOfBuffer: true
|
||||
};
|
||||
|
||||
// Represents the keystroke that triggered the suggestion. It's not technically part
|
||||
// of the Context when the suggestion is built.
|
||||
let postTransform = {
|
||||
insert: 'l',
|
||||
deleteLeft: 0
|
||||
};
|
||||
|
||||
let options = {
|
||||
punctuation: englishPunctuation
|
||||
};
|
||||
|
||||
let model = new models.TrieModel(jsonFixture('models/tries/english-1000'), options);
|
||||
let compositor = new ModelCompositor(model);
|
||||
let baseContextState = compositor.contextTracker.analyzeState(model, baseContext);
|
||||
|
||||
baseContextState.tail.replacements = [{
|
||||
suggestion: baseSuggestion,
|
||||
tokenWidth: 1
|
||||
}];
|
||||
|
||||
let reversion = compositor.acceptSuggestion(baseSuggestion, baseContext, postTransform);
|
||||
|
||||
// Actual test assertion - was the replacement tracked?
|
||||
assert.equal(baseContextState.tail.activeReplacementId, baseSuggestion.id);
|
||||
assert.equal(reversion.id, -baseSuggestion.id);
|
||||
|
||||
// Next step - on the followup context, is the replacement still active?
|
||||
let postContext = models.applyTransform(baseSuggestion.transform, baseContext);
|
||||
let postContextState = compositor.contextTracker.analyzeState(model, postContext);
|
||||
|
||||
// Penultimate token corresponds to whitespace, which does not have a 'raw' representation.
|
||||
assert.isNull(postContextState.tokens[postContextState.tokens.length - 2].raw);
|
||||
|
||||
// Final token is empty (follows a wordbreak)
|
||||
assert.equal(postContextState.tail.raw, '');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
// @ts-check
|
||||
import BASE_CONFIG from './web-test-runner.config.mjs';
|
||||
import teamcityReporter from '@keymanapp/common-test-resources/test-runner-TC-reporter.mjs';
|
||||
import { sessionStabilityReporter } from '@keymanapp/common-test-resources/test-runner-stability-reporter.mjs';
|
||||
|
||||
/** @type {import('@web/test-runner').TestRunnerConfig} */
|
||||
export default {
|
||||
...BASE_CONFIG,
|
||||
reporters: [
|
||||
teamcityReporter(), /* custom-written, for CI-friendly reports */
|
||||
sessionStabilityReporter({ciMode: true})
|
||||
]
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"extends": "../../models/tsconfig.kmw-worker-base.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "build/obj",
|
||||
"tsBuildInfoFile": "build/obj/tsconfig.tsbuildinfo",
|
||||
"rootDir": "./src/main",
|
||||
|
||||
// As this one is the one that directly interfaces with the worker (from the inside)
|
||||
"lib": ["webworker", "es6"],
|
||||
},
|
||||
"references": [
|
||||
// types
|
||||
{ "path": "../../models/types" },
|
||||
{ "path": "../lm-message-types" },
|
||||
// modules
|
||||
{ "path": "../keyman-version" },
|
||||
{ "path": "../utils" },
|
||||
{ "path": "../../models/templates" },
|
||||
{ "path": "../../models/wordbreakers" },
|
||||
],
|
||||
"include": [
|
||||
"src/main/**/*.ts"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"extends": "../tsconfig.kmw-main-base.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"types": ["node"],
|
||||
"baseUrl": "./",
|
||||
"outDir": "build/obj/",
|
||||
"tsBuildInfoFile": "build/obj/tsconfig.tsbuildinfo",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
|
||||
"references": [
|
||||
{ "path": "../keyman-version" },
|
||||
{ "path": "../utils/" },
|
||||
{ "path": "../keyboard-processor/" },
|
||||
{ "path": "../lm-message-types" }
|
||||
],
|
||||
}
|
||||
|
|
@ -73,40 +73,25 @@ function compile_schemas() {
|
|||
node tools/schema-bundler.js
|
||||
}
|
||||
|
||||
function copy_cldr_imports() {
|
||||
# Store CLDR imports
|
||||
# load all versions that have a cldr_info.json
|
||||
for CLDR_INFO_PATH in "$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/"*/cldr_info.json
|
||||
do
|
||||
# TODO-LDML: developer/src/inst/download.in.mak needs these also...
|
||||
CLDR_PATH=$(dirname "$CLDR_INFO_PATH")
|
||||
CLDR_VER=$(basename "$CLDR_PATH")
|
||||
mkdir -p "$THIS_SCRIPT_PATH/build/src/import/$CLDR_VER"
|
||||
# TODO-LDML: When these are copied, the DOCTYPE will break due to the wrong path. We don't use the DTD so it should be OK.
|
||||
cp "$CLDR_INFO_PATH" "$CLDR_PATH/import/"*.xml "$THIS_SCRIPT_PATH/build/src/import/$CLDR_VER/"
|
||||
done
|
||||
}
|
||||
|
||||
function do_configure() {
|
||||
compile_schemas
|
||||
verify_npm_setup
|
||||
}
|
||||
|
||||
function do_build() {
|
||||
copy_cldr_imports
|
||||
tsc --build
|
||||
}
|
||||
|
||||
function do_test() {
|
||||
eslint .
|
||||
tsc --build test
|
||||
c8 --skip-full --reporter=lcov --reporter=text mocha "${builder_extra_params[@]}"
|
||||
readonly C8_THRESHOLD=50
|
||||
c8 -skip-full --reporter=lcov --reporter=text --lines $C8_THRESHOLD --statements $C8_THRESHOLD --branches $C8_THRESHOLD --functions $C8_THRESHOLD mocha "${builder_extra_params[@]}"
|
||||
builder_echo warning "Coverage thresholds are currently $C8_THRESHOLD%, which is lower than ideal."
|
||||
builder_echo warning "Please increase threshold in build.sh as test coverage improves."
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
builder_run_action clean rm -rf ./build/ ./tsconfig.tsbuildinfo
|
||||
builder_run_action configure do_configure
|
||||
builder_run_action build do_build
|
||||
builder_run_action build tsc --build
|
||||
builder_run_action test do_test
|
||||
builder_run_action publish builder_publish_npm
|
||||
|
|
|
|||
|
|
@ -31,24 +31,17 @@
|
|||
"dependencies": {
|
||||
"@keymanapp/ldml-keyboard-constants": "*",
|
||||
"@keymanapp/keyman-version": "*",
|
||||
"restructure": "3.0.1",
|
||||
"semver": "^7.5.2",
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
"restructure": "3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/git-diff": "^2.0.3",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "^20.4.1",
|
||||
"@types/semver": "^7.3.12",
|
||||
"@types/xml2js": "^0.4.5",
|
||||
"ajv": "^8.12.0",
|
||||
"ajv-cli": "^5.0.0",
|
||||
"ajv-formats": "^2.1.1",
|
||||
"c8": "^7.12.0",
|
||||
"chalk": "^2.4.2",
|
||||
"git-diff": "^2.0.6",
|
||||
"hexy": "^0.3.4",
|
||||
"mocha": "^8.4.0",
|
||||
"typescript": "^5.4.5"
|
||||
|
|
|
|||
33
common/web/types/src/consts/modifier-key-constants.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*
|
||||
* Modifier key bit-flags
|
||||
*/
|
||||
|
||||
export const ModifierKeyConstants = {
|
||||
// Define Keyman Developer modifier bit-flags (exposed for use by other modules)
|
||||
// Compare against /common/include/kmx_file.h. CTRL+F "#define LCTRLFLAG" to find the secton.
|
||||
LCTRLFLAG: 0x0001, // Left Control flag
|
||||
RCTRLFLAG: 0x0002, // Right Control flag
|
||||
LALTFLAG: 0x0004, // Left Alt flag
|
||||
RALTFLAG: 0x0008, // Right Alt flag
|
||||
K_SHIFTFLAG: 0x0010, // Either shift flag
|
||||
K_CTRLFLAG: 0x0020, // Either ctrl flag
|
||||
K_ALTFLAG: 0x0040, // Either alt flag
|
||||
K_METAFLAG: 0x0080, // Either Meta-key flag (tentative). Not usable in keyboard rules;
|
||||
// Used internally (currently, only by KMW) to ensure Meta-key
|
||||
// shortcuts safely bypass rules
|
||||
// Meta key = Command key on macOS, Windows key on Windows/Linux
|
||||
CAPITALFLAG: 0x0100, // Caps lock on
|
||||
NOTCAPITALFLAG: 0x0200, // Caps lock NOT on
|
||||
NUMLOCKFLAG: 0x0400, // Num lock on
|
||||
NOTNUMLOCKFLAG: 0x0800, // Num lock NOT on
|
||||
SCROLLFLAG: 0x1000, // Scroll lock on
|
||||
NOTSCROLLFLAG: 0x2000, // Scroll lock NOT on
|
||||
ISVIRTUALKEY: 0x4000, // It is a Virtual Key Sequence
|
||||
VIRTUALCHARKEY: 0x8000, // Keyman 6.0: Virtual Key Cap Sequence NOT YET
|
||||
|
||||
// Note: OTHER_MODIFIER = 0x10000, used by KMX+ for the
|
||||
// other modifier flag in layers, > 16 bit so not available here.
|
||||
// See keys_mod_other in keyman_core_ldml.ts
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
// Define standard keycode numbers (exposed for use by other modules)
|
||||
// TODO-LDML: merge with common\web\keyboard-processor\src\text\codes.ts
|
||||
|
||||
/**
|
||||
* May include non-US virtual key codes
|
||||
|
|
@ -128,7 +127,7 @@ export const USVirtualKeyCodes = {
|
|||
'k_?C1':193,
|
||||
K_oDF:0xDF,
|
||||
K_ODF:0xDF,
|
||||
/*K_LOPT:50001,
|
||||
K_LOPT:50001,
|
||||
K_ROPT:50002,
|
||||
K_NUMERALS:50003,
|
||||
K_SYMBOLS:50004,
|
||||
|
|
@ -139,7 +138,7 @@ export const USVirtualKeyCodes = {
|
|||
K_SHIFTED:50009,
|
||||
K_ALTGR:50010,
|
||||
K_TABBACK:50011,
|
||||
K_TABFWD:50012*/
|
||||
K_TABFWD:50012
|
||||
};
|
||||
|
||||
const k = USVirtualKeyCodes;
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export const PRIVATE_USE_IDS = [
|
|||
] as const;
|
||||
|
||||
/* A map of key field names with values matching the `typeof` the corresponding property
|
||||
* exists in common/web/keyboard-processor, keyboards/activeLayout.ts.
|
||||
* exists in /web/src/engine/keyboard/src/keyboards/activeLayout.ts.
|
||||
*
|
||||
* 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. */
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
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 { MATCH_HEX_ESCAPE } from '../util/consts.js';
|
||||
import { unescapeOneQuadString } from '../util/util.js';
|
||||
import { ElementParser, ElementSegment, ElementType } from '../../ldml-keyboard/pattern-parser.js';
|
||||
import { util } from '@keymanapp/common-types';
|
||||
import MATCH_HEX_ESCAPE = util.MATCH_HEX_ESCAPE;
|
||||
import unescapeOneQuadString = util.unescapeOneQuadString;
|
||||
|
||||
export enum ElemElementFlags {
|
||||
none = 0,
|
||||
|
|
@ -1,12 +1,18 @@
|
|||
import { constants } from '@keymanapp/ldml-keyboard-constants';
|
||||
import * as r from 'restructure';
|
||||
import { ElementString } from './element-string.js';
|
||||
import { ListItem } from './string-list.js';
|
||||
import { isOneChar, toOneChar, unescapeString, escapeStringForRegex } from '../util/util.js';
|
||||
import { KMXFile } from './kmx.js';
|
||||
import { UnicodeSetParser, UnicodeSet } from '@keymanapp/common-types';
|
||||
import { VariableParser } from '../ldml-keyboard/pattern-parser.js';
|
||||
import { MarkerParser } from '../ldml-keyboard/pattern-parser.js';
|
||||
import { ListItem } from '../../ldml-keyboard/string-list.js';
|
||||
import { util } from '@keymanapp/common-types';
|
||||
import { KMX } from '@keymanapp/common-types';
|
||||
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';
|
||||
|
||||
import isOneChar = util.isOneChar;
|
||||
import toOneChar = util.toOneChar;
|
||||
import unescapeString = util.unescapeString;
|
||||
import escapeStringForRegex = util.escapeStringForRegex;
|
||||
import KMXFile = KMX.KMXFile;
|
||||
|
||||
// Implementation of file structures from /core/src/ldml/C7043_ldml.md
|
||||
// Writer in kmx-builder.ts
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import * as r from 'restructure';
|
||||
import { ModifierKeyConstants } from '../consts/modifier-key-constants.js';
|
||||
|
||||
/* Definitions from kmx_file.h. Must be kept in sync */
|
||||
|
||||
|
|
@ -343,25 +344,25 @@ export class KMXFile {
|
|||
public static readonly HK_CTRL = 0x00020000;
|
||||
public static readonly HK_SHIFT = 0x00040000;
|
||||
|
||||
public static readonly LCTRLFLAG = 0x0001; // Left Control flag
|
||||
public static readonly RCTRLFLAG = 0x0002; // Right Control flag
|
||||
public static readonly LALTFLAG = 0x0004; // Left Alt flag
|
||||
public static readonly RALTFLAG = 0x0008; // Right Alt flag
|
||||
public static readonly K_SHIFTFLAG = 0x0010; // Either shift flag
|
||||
public static readonly K_CTRLFLAG = 0x0020; // Either ctrl flag
|
||||
public static readonly K_ALTFLAG = 0x0040; // Either alt flag
|
||||
public static readonly LCTRLFLAG = ModifierKeyConstants.LCTRLFLAG; // Left Control flag
|
||||
public static readonly RCTRLFLAG = ModifierKeyConstants.RCTRLFLAG; // Right Control flag
|
||||
public static readonly LALTFLAG = ModifierKeyConstants.LALTFLAG; // Left Alt flag
|
||||
public static readonly RALTFLAG = ModifierKeyConstants.RALTFLAG; // Right Alt flag
|
||||
public static readonly K_SHIFTFLAG = ModifierKeyConstants.K_SHIFTFLAG; // Either shift flag
|
||||
public static readonly K_CTRLFLAG = ModifierKeyConstants.K_CTRLFLAG; // Either ctrl flag
|
||||
public static readonly K_ALTFLAG = ModifierKeyConstants.K_ALTFLAG; // Either alt flag
|
||||
//public static readonly K_METAFLAG = 0x0080; // Either Meta-key flag (tentative). Not usable in keyboard rules;
|
||||
// Used internally (currently, only by KMW) to ensure Meta-key
|
||||
// shortcuts safely bypass rules
|
||||
// Meta key = Command key on macOS, Windows key on Windows
|
||||
public static readonly CAPITALFLAG = 0x0100; // Caps lock on
|
||||
public static readonly NOTCAPITALFLAG = 0x0200; // Caps lock NOT on
|
||||
public static readonly NUMLOCKFLAG = 0x0400; // Num lock on
|
||||
public static readonly NOTNUMLOCKFLAG = 0x0800; // Num lock NOT on
|
||||
public static readonly SCROLLFLAG = 0x1000; // Scroll lock on
|
||||
public static readonly NOTSCROLLFLAG = 0x2000; // Scroll lock NOT on
|
||||
public static readonly ISVIRTUALKEY = 0x4000; // It is a Virtual Key Sequence
|
||||
public static readonly VIRTUALCHARKEY = 0x8000; // Keyman 6.0: Virtual Key Cap Sequence NOT YET
|
||||
public static readonly CAPITALFLAG = ModifierKeyConstants.CAPITALFLAG; // Caps lock on
|
||||
public static readonly NOTCAPITALFLAG = ModifierKeyConstants.NOTCAPITALFLAG; // Caps lock NOT on
|
||||
public static readonly NUMLOCKFLAG = ModifierKeyConstants.NUMLOCKFLAG; // Num lock on
|
||||
public static readonly NOTNUMLOCKFLAG = ModifierKeyConstants.NOTNUMLOCKFLAG; // Num lock NOT on
|
||||
public static readonly SCROLLFLAG = ModifierKeyConstants.SCROLLFLAG; // Scroll lock on
|
||||
public static readonly NOTSCROLLFLAG = ModifierKeyConstants.NOTSCROLLFLAG; // Scroll lock NOT on
|
||||
public static readonly ISVIRTUALKEY = ModifierKeyConstants.ISVIRTUALKEY; // It is a Virtual Key Sequence
|
||||
public static readonly VIRTUALCHARKEY = ModifierKeyConstants.VIRTUALCHARKEY; // Keyman 6.0: Virtual Key Cap Sequence NOT YET
|
||||
|
||||
// Note: OTHER_MODIFIER = 0x10000, used by KMX+ for the
|
||||
// other modifier flag in layers, > 16 bit so not available here.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { OrderedStringList } from '../ldml-keyboard/pattern-parser.js';
|
||||
import { DependencySections, StrsItem, StrsOptions } from './kmx-plus.js';
|
||||
import { OrderedStringList } from './pattern-parser.js';
|
||||
import { DependencySections, StrsItem, StrsOptions } from '../kmx/kmx-plus/kmx-plus.js';
|
||||
|
||||
/**
|
||||
* A single entry in a ListItem.
|
||||
|
|
@ -1,53 +1,19 @@
|
|||
export * as KMX from './kmx/kmx.js';
|
||||
export * as KMXPlus from './kmx/kmx-plus.js';
|
||||
export { default as KMXBuilder } from './kmx/kmx-builder.js';
|
||||
export { KmxFileReader, KmxFileReaderError } from './kmx/kmx-file-reader.js';
|
||||
export * as KeymanTargets from './kmx/keyman-targets.js';
|
||||
|
||||
export * as VisualKeyboard from './kvk/visual-keyboard.js';
|
||||
export { default as KMXPlusBuilder} from './kmx/kmx-plus-builder/kmx-plus-builder.js';
|
||||
export { default as KvkFileReader } from './kvk/kvk-file-reader.js';
|
||||
export { default as KvksFileReader } from './kvk/kvks-file-reader.js';
|
||||
export { default as KvkFileWriter } from './kvk/kvk-file-writer.js';
|
||||
export { default as KvksFileWriter } from './kvk/kvks-file-writer.js';
|
||||
export * as KvkFile from './kvk/kvk-file.js';
|
||||
export * as KvksFile from './kvk/kvks-file.js';
|
||||
|
||||
export * as LDMLKeyboard from './ldml-keyboard/ldml-keyboard-xml.js';
|
||||
export { LDMLKeyboardTestDataXMLSourceFile } from './ldml-keyboard/ldml-keyboard-testdata-xml.js';
|
||||
export { UnicodeSetParser, UnicodeSet } from './ldml-keyboard/unicodeset-parser-api.js';
|
||||
export { VariableParser, MarkerParser } from './ldml-keyboard/pattern-parser.js';
|
||||
export { LDMLKeyboardXMLSourceFileReader, LDMLKeyboardXMLSourceFileReaderOptions } from './ldml-keyboard/ldml-keyboard-xml-reader.js';
|
||||
|
||||
export { USVirtualKeyCodes } from './consts/virtual-key-constants.js';
|
||||
export * as Constants from './consts/virtual-key-constants.js';
|
||||
|
||||
export { defaultCompilerOptions, CompilerBaseOptions, CompilerCallbacks, CompilerOptions, CompilerEvent, CompilerErrorNamespace,
|
||||
CompilerErrorSeverity, CompilerPathCallbacks, CompilerFileSystemCallbacks, CompilerCallbackOptions,
|
||||
CompilerError, CompilerMessageSpec, CompilerMessageSpecWithException, compilerErrorSeverity, CompilerErrorMask, CompilerFileCallbacks, compilerErrorSeverityName,
|
||||
compilerErrorFormatCode, CompilerMessageDef,
|
||||
compilerLogLevelToSeverity, CompilerLogLevel, compilerEventFormat, ALL_COMPILER_LOG_LEVELS,
|
||||
ALL_COMPILER_LOG_FORMATS, CompilerLogFormat,
|
||||
CompilerMessageOverride,
|
||||
CompilerMessageOverrideMap,
|
||||
|
||||
KeymanCompilerArtifact,
|
||||
KeymanCompilerArtifactOptional,
|
||||
KeymanCompilerArtifacts,
|
||||
KeymanCompilerResult,
|
||||
KeymanCompiler
|
||||
|
||||
} from './util/compiler-interfaces.js';
|
||||
export { CommonTypesMessages } from './util/common-events.js';
|
||||
export { ModifierKeyConstants } from './consts/modifier-key-constants.js';
|
||||
|
||||
export * as TouchLayout from './keyman-touch-layout/keyman-touch-layout-file.js';
|
||||
export { TouchLayoutFileReader } from './keyman-touch-layout/keyman-touch-layout-file-reader.js';
|
||||
export { TouchLayoutFileWriter, TouchLayoutFileWriterOptions } from './keyman-touch-layout/keyman-touch-layout-file-writer.js';
|
||||
|
||||
export * as KPJ from './kpj/kpj-file.js';
|
||||
export { KPJFileReader } from './kpj/kpj-file-reader.js';
|
||||
export { KeymanDeveloperProject, KeymanDeveloperProjectFile, KeymanDeveloperProjectType, } from './kpj/keyman-developer-project.js';
|
||||
|
||||
export * as KpsFile from './package/kps-file.js';
|
||||
export * as KmpJsonFile from './package/kmp-json-file.js';
|
||||
|
||||
export { Uni_IsSurrogate1, Uni_IsSurrogate2 } from './util/util.js';
|
||||
|
|
@ -58,4 +24,8 @@ export * as KeymanFileTypes from './util/file-types.js';
|
|||
export * as Schemas from './schemas.js';
|
||||
export * as SchemaValidators from './schema-validators.js';
|
||||
|
||||
export * as xml2js from './deps/xml2js/xml2js.js';
|
||||
export * as KMXPlus from './kmx/kmx-plus/kmx-plus.js';
|
||||
// TODO: these exports are really not well named
|
||||
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';
|
||||
|
|
|
|||