mirror of
https://github.com/fcitx5-android/fcitx5-android.git
synced 2026-08-02 04:44:35 +08:00
feat: Calculator candidates
This commit is contained in:
parent
e205195f38
commit
8cdd708c3f
@ -144,6 +144,12 @@ class AppPrefs(private val sharedPreferences: SharedPreferences) {
|
||||
|
||||
val showVoiceInputButton =
|
||||
switch(R.string.show_voice_input_button, "show_voice_input_button", false)
|
||||
val calculatorCandidates = switch(
|
||||
R.string.calculator_candidates,
|
||||
"calculator_candidates",
|
||||
true,
|
||||
R.string.calculator_candidates_summary
|
||||
)
|
||||
val preferredVoiceInput = voiceInputPreference(
|
||||
R.string.preferred_voice_input, "preferred_voice_input", ""
|
||||
) { showVoiceInputButton.getValue() }
|
||||
|
||||
@ -17,17 +17,21 @@ import androidx.annotation.Keep
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import org.fcitx.fcitx5.android.R
|
||||
import org.fcitx.fcitx5.android.core.CandidateWord
|
||||
import org.fcitx.fcitx5.android.core.CapabilityFlags
|
||||
import org.fcitx.fcitx5.android.core.FcitxEvent
|
||||
import org.fcitx.fcitx5.android.daemon.FcitxConnection
|
||||
import org.fcitx.fcitx5.android.daemon.launchOnReady
|
||||
import org.fcitx.fcitx5.android.data.prefs.AppPrefs
|
||||
import org.fcitx.fcitx5.android.data.prefs.ManagedPreference
|
||||
import org.fcitx.fcitx5.android.data.prefs.ManagedPreferenceProvider
|
||||
import org.fcitx.fcitx5.android.data.theme.Theme
|
||||
import org.fcitx.fcitx5.android.data.theme.ThemeManager
|
||||
import org.fcitx.fcitx5.android.input.bar.KawaiiBarComponent
|
||||
import org.fcitx.fcitx5.android.input.broadcast.InputBroadcaster
|
||||
import org.fcitx.fcitx5.android.input.calculator.CalculatorEngine
|
||||
import org.fcitx.fcitx5.android.input.broadcast.PreeditEmptyStateComponent
|
||||
import timber.log.Timber
|
||||
import org.fcitx.fcitx5.android.input.broadcast.PunctuationComponent
|
||||
import org.fcitx.fcitx5.android.input.broadcast.ReturnKeyDrawableComponent
|
||||
import org.fcitx.fcitx5.android.input.candidates.horizontal.HorizontalCandidateComponent
|
||||
@ -105,6 +109,7 @@ class InputView(
|
||||
private val kawaiiBar = KawaiiBarComponent()
|
||||
private val horizontalCandidate = HorizontalCandidateComponent()
|
||||
private val keyboardWindow = KeyboardWindow()
|
||||
private val calculatorEngine = CalculatorEngine()
|
||||
private val symbolPicker = symbolPicker()
|
||||
private val emojiPicker = emojiPicker()
|
||||
private val emoticonPicker = emoticonPicker()
|
||||
@ -131,6 +136,21 @@ class InputView(
|
||||
private val keyboardPrefs = AppPrefs.getInstance().keyboard
|
||||
|
||||
private val focusChangeResetKeyboard by keyboardPrefs.focusChangeResetKeyboard
|
||||
private var calculatorCandidates by keyboardPrefs.calculatorCandidates
|
||||
|
||||
@Keep
|
||||
private val onCalculatorCandidatesChangeListener =
|
||||
ManagedPreference.OnChangeListener<Boolean> { _, value ->
|
||||
if (!value && calculatorEngine.hasExpression) {
|
||||
calculatorEngine.reset()
|
||||
horizontalCandidate.calculatorMode = false
|
||||
horizontalCandidate.adapter.updateCandidates(emptyArray(), 0)
|
||||
broadcaster.onPreeditEmptyStateUpdate(true)
|
||||
broadcaster.onCandidateUpdate(
|
||||
FcitxEvent.CandidateListEvent.Data(0, emptyArray())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val keyboardHeightPercent = keyboardPrefs.keyboardHeightPercent
|
||||
private val keyboardHeightPercentLandscape = keyboardPrefs.keyboardHeightPercentLandscape
|
||||
@ -188,6 +208,62 @@ class InputView(
|
||||
// MUST call before any operation
|
||||
setupScope()
|
||||
|
||||
commonKeyActionListener.onSymAction = fun(sym: Int): Boolean {
|
||||
if (!calculatorCandidates) return false
|
||||
Timber.d("Calculator: onSymAction called sym=0x%04x, isNumberKeyboard=%s, hasExpression=%s",
|
||||
sym, keyboardWindow.isNumberKeyboardActive, calculatorEngine.hasExpression)
|
||||
if (!keyboardWindow.isNumberKeyboardActive) {
|
||||
if (calculatorEngine.hasExpression) {
|
||||
Timber.d("Calculator: resetting stale expression")
|
||||
calculatorEngine.reset()
|
||||
}
|
||||
return false
|
||||
}
|
||||
val canHandle = calculatorEngine.canHandleSym(sym)
|
||||
val result = canHandle && calculatorEngine.handleSym(sym)
|
||||
Timber.d("Calculator: canHandle=%s, handleSym result=%s, expression=%s",
|
||||
canHandle, result, calculatorEngine.currentExpression)
|
||||
return result
|
||||
}
|
||||
|
||||
calculatorEngine.onCandidatesChanged = { candidates, expression ->
|
||||
Timber.d("Calculator: onCandidatesChanged candidates=%d expression=%s mode=%s",
|
||||
candidates.size, expression, horizontalCandidate.calculatorMode)
|
||||
if (expression.isNotEmpty()) {
|
||||
service.currentInputConnection?.setComposingText(expression, 1)
|
||||
} else {
|
||||
service.currentInputConnection?.finishComposingText()
|
||||
}
|
||||
if (candidates.isNotEmpty()) {
|
||||
horizontalCandidate.calculatorMode = true
|
||||
horizontalCandidate.onCalculatorCandidateClick = { text ->
|
||||
Timber.d("Calculator: candidate clicked text=%s", text)
|
||||
service.currentInputConnection?.commitText(text, 1)
|
||||
calculatorEngine.reset()
|
||||
}
|
||||
horizontalCandidate.adapter.updateCandidates(candidates.toTypedArray(), candidates.size)
|
||||
} else {
|
||||
if (horizontalCandidate.calculatorMode) {
|
||||
horizontalCandidate.calculatorMode = false
|
||||
horizontalCandidate.adapter.updateCandidates(emptyArray(), 0)
|
||||
}
|
||||
}
|
||||
// trigger bar state machine transition to show/hide candidate view
|
||||
broadcaster.onPreeditEmptyStateUpdate(expression.isEmpty())
|
||||
broadcaster.onCandidateUpdate(
|
||||
FcitxEvent.CandidateListEvent.Data(candidates.size, candidates.toTypedArray())
|
||||
)
|
||||
}
|
||||
|
||||
calculatorEngine.onCommitResult = { result ->
|
||||
Timber.d("Calculator: onCommitResult result=%s", result)
|
||||
service.currentInputConnection?.commitText(result, 1)
|
||||
horizontalCandidate.calculatorMode = false
|
||||
horizontalCandidate.adapter.updateCandidates(emptyArray(), 0)
|
||||
broadcaster.onPreeditEmptyStateUpdate(true)
|
||||
broadcaster.onCandidateUpdate(FcitxEvent.CandidateListEvent.Data(0, emptyArray()))
|
||||
}
|
||||
|
||||
// restore punctuation mapping in case of InputView recreation
|
||||
fcitx.launchOnReady {
|
||||
punctuation.updatePunctuationMapping(it.statusAreaActionsCached)
|
||||
@ -258,6 +334,7 @@ class InputView(
|
||||
})
|
||||
|
||||
keyboardPrefs.registerOnChangeListener(onKeyboardSizeChangeListener)
|
||||
keyboardPrefs.calculatorCandidates.registerOnChangeListener(onCalculatorCandidatesChangeListener)
|
||||
}
|
||||
|
||||
private fun updateKeyboardSize() {
|
||||
@ -309,6 +386,11 @@ class InputView(
|
||||
* called when [InputView] is about to show, or restart
|
||||
*/
|
||||
fun startInput(info: EditorInfo, capFlags: CapabilityFlags, restarting: Boolean = false) {
|
||||
Timber.d("Calculator: startInput inputType=0x%08x", info.inputType)
|
||||
calculatorEngine.reset()
|
||||
horizontalCandidate.calculatorMode = false
|
||||
broadcaster.onPreeditEmptyStateUpdate(true)
|
||||
broadcaster.onCandidateUpdate(FcitxEvent.CandidateListEvent.Data(0, emptyArray()))
|
||||
broadcaster.onStartInput(info, capFlags)
|
||||
returnKeyDrawable.updateDrawableOnEditorInfo(info)
|
||||
if (focusChangeResetKeyboard || !restarting) {
|
||||
@ -330,6 +412,12 @@ class InputView(
|
||||
}
|
||||
|
||||
override fun handleFcitxEvent(it: FcitxEvent<*>) {
|
||||
if (calculatorEngine.hasExpression && !keyboardWindow.isNumberKeyboardActive) {
|
||||
Timber.d("Calculator: stale expression reset on fcitx event")
|
||||
calculatorEngine.reset()
|
||||
broadcaster.onPreeditEmptyStateUpdate(true)
|
||||
broadcaster.onCandidateUpdate(FcitxEvent.CandidateListEvent.Data(0, emptyArray()))
|
||||
}
|
||||
when (it) {
|
||||
is FcitxEvent.CandidateListEvent -> {
|
||||
broadcaster.onCandidateUpdate(it.data)
|
||||
@ -364,6 +452,7 @@ class InputView(
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
keyboardPrefs.unregisterOnChangeListener(onKeyboardSizeChangeListener)
|
||||
keyboardPrefs.calculatorCandidates.unregisterOnChangeListener(onCalculatorCandidatesChangeListener)
|
||||
// clear DynamicScope, implies that InputView should not be attached again after detached.
|
||||
scope.clear()
|
||||
super.onDetachedFromWindow()
|
||||
|
||||
@ -0,0 +1,130 @@
|
||||
package org.fcitx.fcitx5.android.input.calculator
|
||||
|
||||
import org.fcitx.fcitx5.android.core.CandidateWord
|
||||
import timber.log.Timber
|
||||
|
||||
class CalculatorEngine {
|
||||
|
||||
companion object {
|
||||
const val KP_0 = 0xffb0
|
||||
const val KP_1 = 0xffb1
|
||||
const val KP_2 = 0xffb2
|
||||
const val KP_3 = 0xffb3
|
||||
const val KP_4 = 0xffb4
|
||||
const val KP_5 = 0xffb5
|
||||
const val KP_6 = 0xffb6
|
||||
const val KP_7 = 0xffb7
|
||||
const val KP_8 = 0xffb8
|
||||
const val KP_9 = 0xffb9
|
||||
const val KP_Add = 0xffab
|
||||
const val KP_Subtract = 0xffad
|
||||
const val KP_Multiply = 0xffaa
|
||||
const val KP_Divide = 0xffaf
|
||||
const val KP_Decimal = 0xffae
|
||||
const val KP_Equal = 0xffbd
|
||||
const val Key_BackSpace = 0xff08
|
||||
|
||||
val calculatorSyms = setOf(
|
||||
KP_0, KP_1, KP_2, KP_3, KP_4, KP_5, KP_6, KP_7, KP_8, KP_9,
|
||||
KP_Add, KP_Subtract, KP_Multiply, KP_Divide, KP_Decimal, KP_Equal,
|
||||
Key_BackSpace
|
||||
)
|
||||
}
|
||||
|
||||
private val expression = StringBuilder()
|
||||
|
||||
var onCandidatesChanged: ((List<CandidateWord>, String) -> Unit)? = null
|
||||
var onCommitResult: ((String) -> Unit)? = null
|
||||
|
||||
val hasExpression: Boolean get() = expression.isNotEmpty()
|
||||
val currentExpression: String get() = expression.toString()
|
||||
|
||||
fun canHandleSym(sym: Int): Boolean = sym in calculatorSyms
|
||||
|
||||
fun handleSym(sym: Int): Boolean {
|
||||
if (sym !in calculatorSyms) return false
|
||||
Timber.d("Calculator: handleSym sym=0x%04x expr=\"%s\"", sym, expression)
|
||||
when (sym) {
|
||||
KP_0, KP_1, KP_2, KP_3, KP_4, KP_5, KP_6, KP_7, KP_8, KP_9 -> {
|
||||
val digit = (sym - KP_0).toString()[0]
|
||||
expression.append(digit)
|
||||
update()
|
||||
}
|
||||
KP_Add -> {
|
||||
appendOperator('+')
|
||||
}
|
||||
KP_Subtract -> {
|
||||
appendOperator('-')
|
||||
}
|
||||
KP_Multiply -> {
|
||||
appendOperator('*')
|
||||
}
|
||||
KP_Divide -> {
|
||||
appendOperator('/')
|
||||
}
|
||||
KP_Decimal -> {
|
||||
if (expression.isEmpty() || expression.last() !in "0123456789.") {
|
||||
expression.append("0")
|
||||
}
|
||||
val lastNumStart = expression.lastIndexOfAny(
|
||||
charArrayOf('+', '-', '*', '/')
|
||||
) + 1
|
||||
val lastNum = expression.substring(lastNumStart)
|
||||
if ('.' !in lastNum) {
|
||||
expression.append('.')
|
||||
}
|
||||
update()
|
||||
}
|
||||
KP_Equal -> {
|
||||
reset()
|
||||
return false
|
||||
}
|
||||
Key_BackSpace -> {
|
||||
if (expression.isNotEmpty()) {
|
||||
expression.deleteCharAt(expression.length - 1)
|
||||
update()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun appendOperator(op: Char) {
|
||||
if (expression.isEmpty()) return
|
||||
val last = expression.last()
|
||||
if (last in "+-*/") {
|
||||
expression.setCharAt(expression.length - 1, op)
|
||||
} else {
|
||||
expression.append(op)
|
||||
}
|
||||
update()
|
||||
}
|
||||
|
||||
private fun update() {
|
||||
val expr = expression.toString()
|
||||
val candidates = mutableListOf<CandidateWord>()
|
||||
val result = ExpressionEvaluator.evaluate(expr)
|
||||
Timber.d("Calculator: update expr=\"%s\" evaluateResult=%s", expr, result)
|
||||
if (result != null) {
|
||||
val resultStr = formatResult(result)
|
||||
candidates.add(CandidateWord("", resultStr, ""))
|
||||
candidates.add(CandidateWord("", "${expr}=${resultStr}", ""))
|
||||
}
|
||||
onCandidatesChanged?.invoke(candidates, expr)
|
||||
}
|
||||
|
||||
private fun formatResult(value: Double): String {
|
||||
return if (value == value.toLong().toDouble()) {
|
||||
value.toLong().toString()
|
||||
} else {
|
||||
value.toString()
|
||||
}
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
expression.clear()
|
||||
onCandidatesChanged?.invoke(emptyList(), "")
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,104 @@
|
||||
package org.fcitx.fcitx5.android.input.calculator
|
||||
|
||||
object ExpressionEvaluator {
|
||||
|
||||
fun evaluate(expression: String): Double? {
|
||||
if (expression.isBlank()) return null
|
||||
val tokens = tokenize(expression) ?: return null
|
||||
if (tokens.isEmpty()) return null
|
||||
return try {
|
||||
val parser = Parser(tokens)
|
||||
val result = parser.parseExpression()
|
||||
if (parser.pos != tokens.size) null else result
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Token {
|
||||
data class Number(val value: Double) : Token()
|
||||
data class Op(val op: Char) : Token()
|
||||
}
|
||||
|
||||
private fun tokenize(expr: String): List<Token>? {
|
||||
val tokens = mutableListOf<Token>()
|
||||
var i = 0
|
||||
while (i < expr.length) {
|
||||
val c = expr[i]
|
||||
when {
|
||||
c.isWhitespace() -> i++
|
||||
c in "0123456789." -> {
|
||||
val start = i
|
||||
var dotCount = 0
|
||||
while (i < expr.length && (expr[i].isDigit() || (expr[i] == '.' && dotCount++ == 0))) i++
|
||||
val numStr = expr.substring(start, i)
|
||||
if (numStr == "." || numStr.endsWith(".") || numStr.count { it == '.' } > 1) return null
|
||||
val value = numStr.toDoubleOrNull() ?: return null
|
||||
tokens.add(Token.Number(value))
|
||||
}
|
||||
c in "+-*/" -> {
|
||||
tokens.add(Token.Op(c))
|
||||
i++
|
||||
}
|
||||
else -> return null
|
||||
}
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
private class Parser(private val tokens: List<Token>) {
|
||||
var pos = 0
|
||||
|
||||
fun parseExpression(): Double {
|
||||
var left = parseTerm()
|
||||
while (pos < tokens.size) {
|
||||
when (val token = tokens[pos]) {
|
||||
is Token.Op -> when (token.op) {
|
||||
'+', '-' -> {
|
||||
pos++
|
||||
val right = parseTerm()
|
||||
left = if (token.op == '+') left + right else left - right
|
||||
}
|
||||
else -> break
|
||||
}
|
||||
else -> break
|
||||
}
|
||||
}
|
||||
return left
|
||||
}
|
||||
|
||||
private fun parseTerm(): Double {
|
||||
var left = parseFactor()
|
||||
while (pos < tokens.size) {
|
||||
when (val token = tokens[pos]) {
|
||||
is Token.Op -> when (token.op) {
|
||||
'*', '/' -> {
|
||||
pos++
|
||||
val right = parseFactor()
|
||||
left = if (token.op == '*') {
|
||||
left * right
|
||||
} else {
|
||||
if (right == 0.0) throw ArithmeticException("Division by zero")
|
||||
left / right
|
||||
}
|
||||
}
|
||||
else -> break
|
||||
}
|
||||
else -> break
|
||||
}
|
||||
}
|
||||
return left
|
||||
}
|
||||
|
||||
private fun parseFactor(): Double {
|
||||
val token = tokens.getOrNull(pos) ?: throw IllegalArgumentException("Unexpected end")
|
||||
return when (token) {
|
||||
is Token.Number -> {
|
||||
pos++
|
||||
token.value
|
||||
}
|
||||
else -> throw IllegalArgumentException("Unexpected token: $token")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -84,6 +84,16 @@ class HorizontalCandidateComponent :
|
||||
)
|
||||
}
|
||||
|
||||
var calculatorMode: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
if (!value) {
|
||||
onCalculatorCandidateClick = null
|
||||
}
|
||||
}
|
||||
|
||||
var onCalculatorCandidateClick: ((String) -> Unit)? = null
|
||||
|
||||
val adapter: HorizontalCandidateViewAdapter by lazy {
|
||||
object : HorizontalCandidateViewAdapter(theme) {
|
||||
override fun onBindViewHolder(holder: CandidateViewHolder, position: Int) {
|
||||
@ -92,12 +102,19 @@ class HorizontalCandidateComponent :
|
||||
minWidth = layoutMinWidth
|
||||
flexGrow = layoutFlexGrow
|
||||
}
|
||||
holder.itemView.setOnClickListener {
|
||||
fcitx.launchOnReady { it.select(holder.idx) }
|
||||
}
|
||||
holder.itemView.setOnLongClickListener {
|
||||
inputView.showCandidateActionMenu(holder.idx, holder.candidate.text, holder.ui.root)
|
||||
true
|
||||
if (calculatorMode) {
|
||||
holder.itemView.setOnClickListener {
|
||||
onCalculatorCandidateClick?.invoke(holder.candidate.text)
|
||||
}
|
||||
holder.itemView.setOnLongClickListener(null)
|
||||
} else {
|
||||
holder.itemView.setOnClickListener {
|
||||
fcitx.launchOnReady { it.select(holder.idx) }
|
||||
}
|
||||
holder.itemView.setOnLongClickListener {
|
||||
inputView.showCandidateActionMenu(holder.idx, holder.candidate.text, holder.ui.root)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,6 +184,7 @@ class HorizontalCandidateComponent :
|
||||
}
|
||||
|
||||
override fun onCandidateUpdate(data: FcitxEvent.CandidateListEvent.Data) {
|
||||
if (calculatorMode) return
|
||||
val candidates = data.candidates
|
||||
val total = data.total
|
||||
val maxSpanCount = maxSpanCountPref.getValue()
|
||||
|
||||
@ -64,6 +64,12 @@ class CommonKeyActionListener :
|
||||
|
||||
private var backspaceSwipeState = Stopped
|
||||
|
||||
/**
|
||||
* Called when a [SymAction] is received before forwarding to fcitx.
|
||||
* Return true to consume the action (prevents forwarding).
|
||||
*/
|
||||
var onSymAction: ((sym: Int) -> Boolean)? = null
|
||||
|
||||
// there should be a new fcitx API for this
|
||||
private suspend fun FcitxAPI.commitAndReset() {
|
||||
if (inputMethodEntryCached.languageCode.startsWith("zh")) {
|
||||
@ -93,8 +99,14 @@ class CommonKeyActionListener :
|
||||
is FcitxKeyAction -> service.postFcitxJob {
|
||||
sendKey(action.act, action.states.states, action.code)
|
||||
}
|
||||
is SymAction -> service.postFcitxJob {
|
||||
sendKey(action.sym, action.states)
|
||||
is SymAction -> {
|
||||
if (onSymAction?.invoke(action.sym.sym) == true) {
|
||||
// consumed by calculator engine, do nothing
|
||||
} else {
|
||||
service.postFcitxJob {
|
||||
sendKey(action.sym, action.states)
|
||||
}
|
||||
}
|
||||
}
|
||||
is CommitAction -> service.postFcitxJob {
|
||||
commitAndReset()
|
||||
|
||||
@ -76,6 +76,9 @@ class KeyboardWindow : InputWindow.SimpleInputWindow<KeyboardWindow>(), Essentia
|
||||
|
||||
private val currentKeyboard: BaseKeyboard? get() = keyboards[currentKeyboardName]
|
||||
|
||||
val isNumberKeyboardActive: Boolean
|
||||
get() = currentKeyboardName == NumberKeyboard.Name
|
||||
|
||||
private val keyActionListener = KeyActionListener { it, source ->
|
||||
if (it is KeyAction.LayoutSwitchAction) {
|
||||
switchLayout(it.act)
|
||||
|
||||
@ -154,6 +154,8 @@
|
||||
<string name="num_items_deleted">%1$d Element(e) gelöscht</string>
|
||||
<string name="plugins">Plugins</string>
|
||||
<string name="show_voice_input_button">Taste Spracheingabe zeigen</string>
|
||||
<string name="calculator_candidates">Taschenrechner-Kandidaten</string>
|
||||
<string name="calculator_candidates_summary">Ergebnisse als Kandidaten auf der Nummerntastatur anzeigen</string>
|
||||
<string name="fcitx_daemon">Fcitx Daemon</string>
|
||||
<string name="removed_n_items"> %1$d Element(e) entfernt</string>
|
||||
<string name="whether_reset_switch_preference">Wollen Sie diese Einstellung auf den Standard zurücksetzen?</string>
|
||||
|
||||
@ -110,6 +110,8 @@
|
||||
<string name="main_program">programa principal</string>
|
||||
<string name="edit_theme">Editar tema</string>
|
||||
<string name="show_voice_input_button">Mostrar botón de entrada por voz</string>
|
||||
<string name="calculator_candidates">Candidatos de calculadora</string>
|
||||
<string name="calculator_candidates_summary">Mostrar resultados de cálculos como candidatos en el teclado numérico</string>
|
||||
<string name="no_plugins">No hay ningún complemento disponible</string>
|
||||
<string name="whether_reset_switch_preference">¿Quiere restablecer la preferencia a su valor predeterminado?</string>
|
||||
<string name="enabled">Activado</string>
|
||||
|
||||
@ -220,6 +220,8 @@
|
||||
<string name="main_program">メインプログラム</string>
|
||||
<string name="edit_theme">テーマを編集</string>
|
||||
<string name="show_voice_input_button">音声入力ボタンを表示する</string>
|
||||
<string name="calculator_candidates">計算機の候補</string>
|
||||
<string name="calculator_candidates_summary">数字キーボードで式を入力すると計算結果が候補として表示されます</string>
|
||||
<string name="fcitx_daemon">Fcitxデーモン</string>
|
||||
<string name="restarting_fcitx">Fcitxを再起動中</string>
|
||||
<string name="plugin_needs_reload">プラグインの変更を除去しました。ここをクリックして再読み込みします</string>
|
||||
|
||||
@ -91,4 +91,6 @@
|
||||
<string name="quickphrase_phrase">구문</string>
|
||||
<string name="reload_config">구성 새로고침</string>
|
||||
<string name="input_method_options">입력기 옵션</string>
|
||||
<string name="calculator_candidates">계산기 후보</string>
|
||||
<string name="calculator_candidates_summary">숫자 키보드에서 표현식을 입력할 때 계산 결과를 후보로 표시</string>
|
||||
</resources>
|
||||
|
||||
@ -225,6 +225,8 @@
|
||||
<string name="main_program">основная программа</string>
|
||||
<string name="edit_theme">Изменить тему</string>
|
||||
<string name="show_voice_input_button">Показывать кнопку голосового ввода</string>
|
||||
<string name="calculator_candidates">Кандидаты калькулятора</string>
|
||||
<string name="calculator_candidates_summary">Показывать результаты вычислений в виде кандидатов на цифровой клавиатуре</string>
|
||||
<string name="fcitx_daemon">Демон Fcitx</string>
|
||||
<string name="restarting_fcitx">Перезапуск Fcitx</string>
|
||||
<string name="plugin_needs_reload">Обнаружены изменения плагина, нажмите здесь, чтобы перезагрузить</string>
|
||||
|
||||
@ -225,6 +225,8 @@
|
||||
<string name="main_program">主程序</string>
|
||||
<string name="edit_theme">编辑主题</string>
|
||||
<string name="show_voice_input_button">显示语音输入按钮</string>
|
||||
<string name="calculator_candidates">计算器候选词</string>
|
||||
<string name="calculator_candidates_summary">在数字键盘上输入表达式时显示计算结果候选词</string>
|
||||
<string name="fcitx_daemon">Fcitx 守护程序</string>
|
||||
<string name="restarting_fcitx">正在重新启动 Fcitx</string>
|
||||
<string name="plugin_needs_reload">检测到插件更改,点此重新加载</string>
|
||||
|
||||
@ -225,6 +225,8 @@
|
||||
<string name="main_program">主程式</string>
|
||||
<string name="edit_theme">編輯主題</string>
|
||||
<string name="show_voice_input_button">顯示語音輸入鍵</string>
|
||||
<string name="calculator_candidates">計算機候選詞</string>
|
||||
<string name="calculator_candidates_summary">在數字鍵盤上輸入表達式時顯示計算結果候選詞</string>
|
||||
<string name="fcitx_daemon">Fcitx 常駐程式</string>
|
||||
<string name="restarting_fcitx">重新啟動 Fcitx</string>
|
||||
<string name="plugin_needs_reload">偵測到有外掛程式變化,點此重新載入</string>
|
||||
|
||||
@ -225,6 +225,8 @@
|
||||
<string name="main_program">main program</string>
|
||||
<string name="edit_theme">Edit Theme</string>
|
||||
<string name="show_voice_input_button">Show voice input button</string>
|
||||
<string name="calculator_candidates">Calculator candidates</string>
|
||||
<string name="calculator_candidates_summary">Show calculation results as candidates on the number keyboard</string>
|
||||
<string name="fcitx_daemon">Fcitx Daemon</string>
|
||||
<string name="restarting_fcitx">Restarting Fcitx</string>
|
||||
<string name="plugin_needs_reload">Plugin changes detected, click here to reload</string>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user