Skip to content

Commit

Permalink
feat: added full translate mode
Browse files Browse the repository at this point in the history
  • Loading branch information
UnluckyNinja committed Dec 3, 2024
1 parent 2413604 commit e3bb9d9
Show file tree
Hide file tree
Showing 13 changed files with 271 additions and 73 deletions.
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* text=auto eol=lf

*.fflate -diff linguist-generated=true
11 changes: 11 additions & 0 deletions __mocks__/$.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

const store = new Map()
export function GM_deleteValue(key){
store.delete(key)
}
export function GM_getValue(key){
store.get(key)
}
export function GM_setValue(key, value) {
store.set(key, value)
}
25 changes: 21 additions & 4 deletions src/components/Options.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Tabs, TabsList, TabsTrigger } from './ui/tabs';
const {
enableGoogleTranslate,
translateMode,
customTranslations,
matchSelectors,
katakanaLanguage,
Expand Down Expand Up @@ -36,6 +37,14 @@ function removeSelector(k: number) {
matchSelectors.value.splice(k)
}
function updateMode(value: boolean){
if(value) {
translateMode.value = 'full'
} else {
translateMode.value = 'katakana'
}
}
</script>

<template>
Expand All @@ -47,25 +56,33 @@ function removeSelector(k: number) {
</CardHeader>
<CardContent class="space-y-xs">
<!-- simple options -->
<div class="flex">
<div class="flex-auto">
全文替换翻译
</div>
<div class="flex justify-end">
<Switch :checked="translateMode === 'full'" @update:checked="updateMode" />
</div>
</div>
<div class="flex">
<div class="flex-auto">
启用片假名谷歌翻译
</div>
<div class="flex justify-end">
<Switch v-model:checked="enableGoogleTranslate" />
<Switch :disabled="translateMode === 'full'" v-model:checked="enableGoogleTranslate" />
</div>
</div>
<div class="flex items-center">
<div class="flex-auto">
谷歌翻译目标语言
片假名翻译目标语言
</div>
<div class="flex justify-end">
<Tabs :default-value="katakanaLanguage">
<TabsList class="grid grid-cols-2">
<TabsTrigger @click="katakanaLanguage = 'zh-CN'" class="data-[state=active]:bg-primary" value="zh-CN">
<TabsTrigger :disabled="translateMode === 'full'" @click="katakanaLanguage = 'zh-CN'" class="data-[state=active]:bg-primary" value="zh-CN">
中文
</TabsTrigger>
<TabsTrigger @click="katakanaLanguage = 'en'" class="data-[state=active]:bg-primary" value="en">
<TabsTrigger :disabled="translateMode === 'full'" @click="katakanaLanguage = 'en'" class="data-[state=active]:bg-primary" value="en">
英文
</TabsTrigger>
</TabsList>
Expand Down
2 changes: 1 addition & 1 deletion src/map.fflate

Large diffs are not rendered by default.

42 changes: 25 additions & 17 deletions src/misc/dawg/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { TrieAutomaton, TrieNode } from './trie'

export type DAWGPatterns<T> = [string, T][] | Map<string, T>

type WalkState = [number, number, TrieNode | null, boolean]
type WalkState = [number, number, TrieNode | null, boolean] //, string]
interface Result {
start: number,
end: number
Expand Down Expand Up @@ -32,8 +32,7 @@ export function useDualDAWG(patterns: string[] | string) {
let rightMostOfFinal = 0
const result: Result[] = []
walkSAM<WalkState>((samNode, state, queue)=>{
let [matched, matching, trieNode, final] = state

let [matched, matching, trieNode, final/*, prefix*/] = state
// final state
if (final) {
const start = wordLength - matching
Expand Down Expand Up @@ -73,7 +72,7 @@ export function useDualDAWG(patterns: string[] | string) {

// postpone final state
if (matched > 0 && finals.get(samNode.id)) {
insertQueue(samNode, [matched, matching, null, true])
insertQueue(samNode, [matched, matching, null, true/*, prefix*/])
}

// trieNode === null -> partly matched only propagate to final state.
Expand All @@ -86,44 +85,53 @@ export function useDualDAWG(patterns: string[] | string) {
for (const char of samNode.next.keys()) {
const nextNode = trie.match(char, trieNode)
if (nextNode) {
insertQueue(samNode.next.get(char)!, [matched, matching + 1, nextNode, false])
insertQueue(samNode.next.get(char)!, [matched, matching + 1, nextNode, false/*, prefix+char*/])
} else {
if (matched > 0) {
insertQueue(samNode.next.get(char)!, [matched, matching + 1, null, false])
insertQueue(samNode.next.get(char)!, [matched, matching + 1, null, false/*, prefix*/])
}
}
}
} else {
for (const char of samNode.next.keys()) {
insertQueue(samNode.next.get(char)!, [matched, matching + 1, null, false])
insertQueue(samNode.next.get(char)!, [matched, matching + 1, null, false/*, prefix*/])
}
}
return queue
}, [sam.root, [0, 0, trie.root, false]])
}, [sam.root, [0, 0, trie.root, false/*, ''*/]])
return result
}
return {
trie,
findWords
}
}

// sort element by the start of substring
// buggy, need futher test
function createCompareByLeftPos(wordLength: number) {
function compareByLeftPos(a: [SuffixNode, WalkState], b: [SuffixNode, WalkState]) {
const samA = a[0]
const stateA = a[1]
const samB = b[0]
const stateB = b[1]
let value
if (a[1][3] !== b[1][3]) { // postpone final
if (a[1][3] === false) return -1
if (stateA[3] !== stateB[3]) { // postpone final
if (stateA[3] === false) return -1
return 1
}

if (samA.id !== samB.id) {
return samA.id - samB.id
}
// sort by starting point of matching
// a[1][3] (final flag) is equal to b[1][3] here
// stateA[3] (final flag) is equal to stateB[3] here
let leftA, leftB
if (a[1][3]) {
leftA = wordLength - a[1][1]
leftB = wordLength - b[1][1]
if (stateA[3]) {
leftA = wordLength - stateA[1]
leftB = wordLength - stateB[1]
} else {
leftA = a[0].len - a[1][1]
leftB = b[0].len - b[1][1]
leftA = samA.len - stateA[1]
leftB = samB.len - stateB[1]
}

value = leftA - leftB
Expand Down
8 changes: 5 additions & 3 deletions src/misc/dawg/trie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ export class TrieAutomaton {
this.changeWord(word, true)
}
private changeWord(word: string, removing: boolean) {

// const newRoot = this.cloneNode(this._root)

// find first confluence state
Expand All @@ -136,7 +135,8 @@ export class TrieAutomaton {
let lastNode = this._root
let mode: 1 | 2 | 3 = 1 // 1: redirect, 2: clone, 3: add
const chain = [lastNode]
for (let i = 0; i < word.length; ++i) {
let i = 0
for (; i < word.length; ++i) {
const char = word.charAt(i)
const child = getTransition(lastNode, char)
if (mode !== 3) {
Expand Down Expand Up @@ -167,7 +167,9 @@ export class TrieAutomaton {
chain.push(lastNode)
}
if (removing) {
lastNode.final = false
if (i=== word.length) {
lastNode.final = false
}
} else {
lastNode.final = true
}
Expand Down
27 changes: 27 additions & 0 deletions src/misc/gameText.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { expect, test, vi } from 'vitest'
import { matchKatakanaOrTerm } from './gameText'

test('japanese example', () => {
vi.mock('./src/misc/store.ts', () => {
return {
useOptions: ()=> ({
enableGoogleTranslate: { value: false },
translateMode: { value: 'full' },
customTranslations: {},
matchSelectors: {},
katakanaLanguage: 'en',
resetOptions: () => null
})
}
})
const text = '「クロの賞状:金賞」「クロの賞状:銀賞」「クロの賞状:銅賞」と引き換えられるご褒美が追加/変更されます。'
const result = matchKatakanaOrTerm(text)
expect(result.map(it => text.slice(it.start, it.end))).toMatchInlineSnapshot(`
[
"クロの賞状:金賞",
"クロの賞状:銀賞",
"クロの賞状:銅賞",
]
`)
vi.unmock('$')
})
16 changes: 9 additions & 7 deletions src/misc/gameText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ import _map from '../map.fflate?raw'
import _trie from '../trie.fflate?raw'
import { useDualDAWG } from './dawg';
import { useOptions } from './store';
import additionalTranslation from '@/translation'

const map = JSON.parse(atou(_map)) as [string, string][]
const gameTextMap = new Map(map)

const engine = useDualDAWG(_trie)
const katakanaRegex = /[\u30A1-\u30FA\u30FD-\u30FF][\u3099\u309A\u30A1-\u30FF]*[\u3099\u309A\u30A1-\u30FA\u30FC-\u30FF]|[\uFF66-\uFF6F\uFF71-\uFF9D][\uFF65-\uFF9F]*[\uFF66-\uFF9F]/y;

Object.keys(additionalTranslation).forEach(w=>{
addWord(w)
})

const { customTranslations } = useOptions()
Object.keys(customTranslations.value).forEach(w=>{
addWord(w)
Expand All @@ -25,13 +30,10 @@ export function matchKatakanaOrTerm(text: string) {
const list = engine.findWords(text)
const lengthMap = new Map(list.map(it=>[it.start, it.end-it.start]))
const results: MatchResult[] = []
const {enableGoogleTranslate} = useOptions()
for (let i = 0; i < text.length; ++i) {
katakanaRegex.lastIndex = i
let match = null
if (enableGoogleTranslate.value) {
match = katakanaRegex.exec(text)
}
match = katakanaRegex.exec(text)
const length = lengthMap.get(i)
if (match) {
if (length && length >= match[0].length) { // -> gameText > match, pick gameText
Expand All @@ -42,7 +44,7 @@ export function matchKatakanaOrTerm(text: string) {
})
i = i + length
--i
} else { // -> match && !gameText, pick match
} else { // -> !gameText || gameText < match, pick match
results.push({
type: 'katakana',
start: i,
Expand All @@ -52,7 +54,7 @@ export function matchKatakanaOrTerm(text: string) {
--i
}
} else {
if (length && length > 0) { // -> !match && gameText, pick match
if (length && length > 0) { // -> !match && gameText, pick gameText
results.push({
type: 'game',
start: i,
Expand All @@ -78,5 +80,5 @@ export function removeWord(word: string) {
export function getBuiltinTranslation(key: string){
// gametext
// customTranslation
return customTranslations.value[key] || gameTextMap.get(key)
return customTranslations.value[key] || additionalTranslation[key] || gameTextMap.get(key)
}
Loading

0 comments on commit e3bb9d9

Please sign in to comment.