├── vanilla ├── scripts │ ├── dutch-tools │ ├── german-tools │ ├── morphology.js │ ├── reader-demo.js │ ├── reader-own-text.js │ ├── reader-alternative │ │ ├── func-utils.js │ │ ├── str-utils.js │ │ ├── text-parsing.js │ │ └── dom-interaction.js │ ├── sandbox │ │ ├── live-timer.js │ │ ├── components │ │ │ ├── word-info.css │ │ │ ├── LiveTimer.js │ │ │ ├── TimeFormatted.js │ │ │ ├── reader-text.css │ │ │ ├── ReaderText.js │ │ │ └── WordInfo.js │ │ ├── reader-text.js │ │ └── reader.js │ ├── languagesAvailable.js │ ├── local-dictionary.js │ ├── ya.translation.js │ ├── layout.js │ ├── reader │ │ ├── initText.js │ │ └── text.js │ ├── dict-export.js │ ├── dict-viewer.js │ ├── reader-alternative.js │ ├── dict.js │ └── reader.js ├── styles │ ├── sandbox.css │ ├── dict-export.css │ ├── dict-review.css │ ├── about.css │ ├── dict-viewer.css │ ├── layout.css │ ├── reader.css │ └── reader-alternative.css ├── dict-review.html ├── dict-export.html ├── sandbox │ ├── live-timer.html │ ├── reader-text.html │ └── reader.html ├── dict-viewer.html ├── reader-alternative.html ├── index.html ├── reader-demo.html └── reader-own-text.html ├── .editorconfig ├── .gitignore ├── screenshot.png ├── specific-language-tools ├── german │ ├── local-dictionary │ │ ├── geschichten.json │ │ ├── menschheiten.json │ │ ├── geschichte.json │ │ ├── kurz.json │ │ ├── menschheit.json │ │ └── unauffällig.json │ ├── morphology │ │ └── affixes │ │ │ ├── test.html │ │ │ ├── circumfixes │ │ │ ├── index.js │ │ │ ├── ge*t.js │ │ │ ├── ver*fachen.js │ │ │ ├── ge*en.js │ │ │ └── ver*en.js │ │ │ ├── prefixes │ │ │ ├── ab*.js │ │ │ ├── ge*.js │ │ │ └── index.js │ │ │ ├── test.js │ │ │ ├── infixes │ │ │ └── index.js │ │ │ ├── interfixes │ │ │ └── index.js │ │ │ ├── index.js │ │ │ └── suffixes │ │ │ └── index.js │ └── text-specific-dictionaries │ │ └── harari.js └── dutch │ └── defaultDict.js └── README.md /vanilla/scripts/dutch-tools: -------------------------------------------------------------------------------- 1 | ../../specific-language-tools/dutch -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = tab 3 | indent_size = 4 4 | -------------------------------------------------------------------------------- /vanilla/scripts/german-tools: -------------------------------------------------------------------------------- 1 | ../../specific-language-tools/german -------------------------------------------------------------------------------- /vanilla/styles/sandbox.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | padding: 1em; 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # MacOS 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | -------------------------------------------------------------------------------- /vanilla/styles/dict-export.css: -------------------------------------------------------------------------------- 1 | #dict-export { 2 | padding: 1em; 3 | } -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbezyuk/dolmetsch/HEAD/screenshot.png -------------------------------------------------------------------------------- /vanilla/styles/dict-review.css: -------------------------------------------------------------------------------- 1 | .dict-review { 2 | max-width: 800px; 3 | margin: 2em auto; 4 | } -------------------------------------------------------------------------------- /vanilla/scripts/morphology.js: -------------------------------------------------------------------------------- 1 | export { test } from './german-tools/morphology/affixes/index.js' 2 | -------------------------------------------------------------------------------- /vanilla/scripts/reader-demo.js: -------------------------------------------------------------------------------- 1 | import initReaderPage from './reader.js' 2 | initReaderPage(true) 3 | -------------------------------------------------------------------------------- /vanilla/scripts/reader-own-text.js: -------------------------------------------------------------------------------- 1 | import initReaderPage from './reader.js' 2 | initReaderPage(false) 3 | -------------------------------------------------------------------------------- /specific-language-tools/german/local-dictionary/geschichten.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "geschichten", 3 | "mayBeFormOf": [ 4 | "geschichte" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /specific-language-tools/german/local-dictionary/menschheiten.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "menschheiten", 3 | "mayBeFormOf": [ 4 | "menschheit" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /specific-language-tools/german/morphology/affixes/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /vanilla/styles/about.css: -------------------------------------------------------------------------------- 1 | .about { 2 | padding: 1em; 3 | } 4 | 5 | footer { 6 | margin-top: 1em; 7 | padding: 1em; 8 | text-align: center; 9 | position: absolute; 10 | bottom: 0; 11 | left: 0; 12 | right: 0; 13 | } 14 | -------------------------------------------------------------------------------- /vanilla/scripts/reader-alternative/func-utils.js: -------------------------------------------------------------------------------- 1 | export function sequence () { 2 | const functionsToCall = [...arguments] 3 | return function () { 4 | for (let f of functionsToCall) { 5 | f.apply(f, arguments) 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /vanilla/scripts/sandbox/live-timer.js: -------------------------------------------------------------------------------- 1 | console.log("Do Androids Dream of Electric Sheep?") 2 | 3 | import LiveTimer from "./components/LiveTimer.js" 4 | 5 | liveTimer.addEventListener('tick', ({ details: now }) => { 6 | console.log('tick!', now) 7 | }) 8 | -------------------------------------------------------------------------------- /specific-language-tools/german/local-dictionary/geschichte.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "geschichte", 3 | "variants": [ 4 | { 5 | "dictionaryForm": "die Geschicte", 6 | "PoS": "noun", 7 | "gender": "feminine", 8 | "translations": { 9 | "en": "history", 10 | "ru": "история" 11 | }, 12 | "forms": [ 13 | "Geschichte" 14 | ] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /specific-language-tools/german/local-dictionary/kurz.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "kurz", 3 | "variants": [ 4 | { 5 | "dictionaryForm": "kurz", 6 | "PoS": "adjective", 7 | "translations": { 8 | "en": "short", 9 | "ru": "короткий" 10 | }, 11 | "forms": [ 12 | "kurz", 13 | "kurze", 14 | "kurzes", 15 | "kurzer" 16 | ] 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /specific-language-tools/german/local-dictionary/menschheit.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "menschheit", 3 | "variants": [ 4 | { 5 | "dictionaryForm": "die Menschheit", 6 | "PoS": "noun", 7 | "gender": "feminine", 8 | "translations": { 9 | "en": "mankind", 10 | "ru": "человечество" 11 | }, 12 | "forms": [ 13 | "Menschheiten" 14 | ], 15 | "constituents": [ 16 | "mensch" 17 | ] 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /specific-language-tools/german/local-dictionary/unauffällig.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "unauffällig", 3 | "variants": [ 4 | { 5 | "dictionaryForm": "unauffällig", 6 | "PoS": "adjective", 7 | "translations": { 8 | "en": "unremarkable", 9 | "ru": "непримечательный" 10 | }, 11 | "forms": [ 12 | "unauffällig", 13 | "unauffällige", 14 | "unauffälliges", 15 | "unauffälliger" 16 | ] 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /vanilla/styles/dict-viewer.css: -------------------------------------------------------------------------------- 1 | #dictTable { 2 | max-width: 800px; 3 | margin: 4em auto; 4 | border-collapse: collapse; 5 | } 6 | #dictTable th, 7 | #dictTable td { 8 | border: 1px solid lightgray; 9 | padding: 1em; 10 | white-space: nowrap; 11 | text-align: left; 12 | } 13 | #dictTable th > br, 14 | #dictTable td > br { 15 | display: none; 16 | } 17 | 18 | #dictTable td:first-child { 19 | font-weight: bold; 20 | } 21 | -------------------------------------------------------------------------------- /specific-language-tools/dutch/defaultDict.js: -------------------------------------------------------------------------------- 1 | export const dict = { 2 | "ru": { 3 | "leuk": "приятно", 4 | "eindelijk": "наконец-то", 5 | "schrijven": "писать", 6 | }, 7 | "en": { 8 | "leuk": "nice", 9 | "eindelijk": "finally", 10 | "schrijven": "to write", 11 | }, 12 | "de": { 13 | "leuk": "nett", 14 | "eindelijk": "endlich", 15 | "schrijven": "schreiben", 16 | }, 17 | "dictionaryForm": { 18 | } 19 | } 20 | 21 | export default dict 22 | -------------------------------------------------------------------------------- /vanilla/dict-review.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Dict Review 16 |
17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /vanilla/scripts/languagesAvailable.js: -------------------------------------------------------------------------------- 1 | export const languagesAvailable = [ 2 | { 3 | "code": "de", 4 | "name": "German", 5 | }, 6 | { 7 | "code": "nl", 8 | "name": "Dutch", 9 | }, 10 | { 11 | "code": "es", 12 | "name": "Spanish", 13 | }, 14 | { 15 | "code": "fr", 16 | "name": "French", 17 | }, 18 | { 19 | "code": "it", 20 | "name": "Italian", 21 | }, 22 | { 23 | "code": "pl", 24 | "name": "Polish", 25 | }, 26 | { 27 | "code": "bg", 28 | "name": "Bulgarian", 29 | }, 30 | ] 31 | 32 | export default languagesAvailable 33 | -------------------------------------------------------------------------------- /vanilla/dict-export.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

15 | 
16 | 	
17 | 
18 | 	
19 | 	
20 | 
21 | 
22 | 
23 | 


--------------------------------------------------------------------------------
/vanilla/sandbox/live-timer.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 
 5 | 	
 6 | 	
 7 | 	
 8 | 
 9 | 
10 | 
11 | 
12 | 	
13 | 
14 | 	
15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /vanilla/sandbox/reader-text.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /specific-language-tools/german/morphology/affixes/circumfixes/index.js: -------------------------------------------------------------------------------- 1 | import * as ge_en from './ge*en.js' 2 | import * as ge_t from './ge*t.js' 3 | import * as ver_en from './ver*en.js' 4 | import * as ver_fachen from './ver*fachen.js' 5 | 6 | export default [ 7 | { 8 | affix: 'ge*en', 9 | affixType: 'circumfix', 10 | ...ge_en, 11 | }, 12 | { 13 | affix: 'ge*t', 14 | affixType: 'circumfix', 15 | ...ge_t, 16 | }, 17 | { 18 | affix: 'ver*en', 19 | affixType: 'circumfix', 20 | ...ver_en, 21 | }, 22 | { 23 | affix: 'ver*fachen', 24 | affixType: 'circumfix', 25 | ...ver_fachen, 26 | }, 27 | ] 28 | -------------------------------------------------------------------------------- /vanilla/scripts/local-dictionary.js: -------------------------------------------------------------------------------- 1 | const cache = {} 2 | 3 | export const requestLocalTranslation = (word) => { 4 | if (!word) { 5 | return 6 | } 7 | if (word in cache) { 8 | return cache[word] ? Promise.resolve(cache[word]) : Promise.reject(cache[word]) 9 | } 10 | return fetch(`/scripts/german-tools/local-dictionary/${word.toLocaleLowerCase()}.json`) 11 | .then((response) => { 12 | if (response.ok) { 13 | return cache[word] = response.json() 14 | } else { 15 | cache[word] = null 16 | throw new Error('Network response was not OK', response) 17 | } 18 | }) 19 | .catch(response => console.log('no local dict entry', response)) 20 | } -------------------------------------------------------------------------------- /vanilla/sandbox/reader.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /vanilla/scripts/reader-alternative/str-utils.js: -------------------------------------------------------------------------------- 1 | export const isNumber = 2 | (word) => word && word.match(/[+-]?\d+([\.|\,]\d+)?/g)?.length === 1 3 | 4 | export function sanitizeWord (word) { 5 | return word.toLocaleLowerCase() 6 | } 7 | 8 | export function smartTrimLeft (s, p) { 9 | let i 10 | for (i = 0; i < s.length; i++) { 11 | const char = s[i] 12 | if (!p.includes(char)) { 13 | break 14 | } 15 | } 16 | return s.slice(i) 17 | } 18 | 19 | 20 | export function smartTrimRight (s, p) { 21 | let i 22 | for (i = s.length - 1; i >= 0; i--) { 23 | const char = s[i] 24 | if (!p.includes(char)) { 25 | break 26 | } 27 | } 28 | return s.slice(0, i + 1) 29 | } 30 | -------------------------------------------------------------------------------- /vanilla/scripts/sandbox/components/word-info.css: -------------------------------------------------------------------------------- 1 | #info { 2 | position: fixed; 3 | background: white; 4 | box-shadow: rgba(0,0,0,0.25) 5px 5px 20px; 5 | left: 0; 6 | right: 0; 7 | top: 0; 8 | text-align: center; 9 | padding: 0.5em; 10 | } 11 | #info > * { 12 | display: inline-block; 13 | margin-right: 3em; 14 | } 15 | 16 | #info > .title { 17 | font-size: 0.5rem; 18 | color: lightgray; 19 | } 20 | 21 | #info #occurences { 22 | color: lightgray; 23 | } 24 | 25 | #info .translation { 26 | display: inline-block; 27 | min-width: 3em; 28 | border-bottom: 1px solid lightgray; 29 | white-space: nowrap; 30 | } 31 | #info > .translation > br { 32 | display: none; 33 | } 34 | -------------------------------------------------------------------------------- /vanilla/scripts/sandbox/reader-text.js: -------------------------------------------------------------------------------- 1 | import ReaderText from "./components/ReaderText.js" 2 | import defaultRawText from "../reader/text.js" 3 | 4 | console.log("Träumen Androiden von elektrischen Schafen?") 5 | 6 | const $readerText = document.getElementById('readerText') 7 | $readerText.setAttribute('raw-text', defaultRawText) 8 | $readerText.addEventListener('selection-change', ({ details: { wordRaw, wordSanitized } }) => { 9 | console.log(wordRaw, wordSanitized) 10 | }) 11 | 12 | console.log('going NL') 13 | $readerText.setAttribute('original-language', 'nl') 14 | // $readerText.dispatchEvent(new Event("vocab-updated")) 15 | 16 | setTimeout(() => { 17 | console.log('going DE') 18 | $readerText.setAttribute('original-language', 'de') 19 | }, 2000) 20 | -------------------------------------------------------------------------------- /specific-language-tools/german/morphology/affixes/circumfixes/ge*t.js: -------------------------------------------------------------------------------- 1 | // see https://en.wiktionary.org/wiki/ge-_-t 2 | 3 | export const exceptions = [ 4 | ] 5 | 6 | export function test (word) { 7 | word = word.toLowerCase() 8 | return word.startsWith('ge') && word.endsWith('t') && !exceptions.includes(word) 9 | } 10 | 11 | export function unwrap (word, unsafe) { 12 | if (unsafe || test(word)) { 13 | return word.slice(2, word.length - 1) 14 | } 15 | } 16 | 17 | export function translate (word, translator) { 18 | if (test (word)) { 19 | return "past participle form of " + translator(unwrap(word, true)) 20 | } 21 | } 22 | 23 | export function annotate (word) { 24 | if (test (word)) { 25 | return "past participle form of " + unwrap(word, true) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /specific-language-tools/german/morphology/affixes/circumfixes/ver*fachen.js: -------------------------------------------------------------------------------- 1 | // see https://en.wiktionary.org/wiki/ver-_-fachen 2 | 3 | export const exceptions = [ 4 | ] 5 | 6 | export function test (word) { 7 | word = word.toLowerCase() 8 | return word.startsWith('ver') && word.endsWith('fachen') && !exceptions.includes(word) 9 | } 10 | 11 | export function unwrap (word, unsafe) { 12 | if (unsafe || isCircumfixed(word)) { 13 | return word.slice(3, word.length - 6) 14 | } 15 | } 16 | 17 | export function translate (word, translator) { 18 | if (isCircumfixed (word)) { 19 | return "multiply by " + translator(unwrap(word, true)) 20 | } 21 | } 22 | 23 | export function annotate (word) { 24 | if (isCircumfixed (word)) { 25 | return "multiply by " + unwrap(word, true) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /vanilla/scripts/sandbox/reader.js: -------------------------------------------------------------------------------- 1 | import ReaderText from "./components/ReaderText.js" 2 | import WordInfo from "./components/WordInfo.js" 3 | import defaultRawText from "../reader/text.js" 4 | 5 | console.log("Träumen Androiden von elektrischen Schafen?") 6 | 7 | const $readerText = document.getElementById('readerText') 8 | const $wordInfo = document.getElementById('wordInfo') 9 | 10 | $readerText.setAttribute('raw-text', defaultRawText) 11 | 12 | $readerText.addEventListener('selection-change', ({ details: { wordRaw, wordSanitized } }) => { 13 | $wordInfo.setAttribute('original-word', wordRaw) 14 | $wordInfo.setAttribute('original-word-sanitized', wordSanitized) 15 | }) 16 | 17 | $wordInfo.addEventListener('translation-updated', (e) => { 18 | $readerText.dispatchEvent(new Event("vocab-updated")) 19 | }) 20 | -------------------------------------------------------------------------------- /specific-language-tools/german/morphology/affixes/circumfixes/ge*en.js: -------------------------------------------------------------------------------- 1 | // see https://en.wiktionary.org/wiki/ge-#German 2 | 3 | export const exceptions = [ 4 | 'gehen' 5 | ] 6 | 7 | export function test (word) { 8 | word = word.toLowerCase() 9 | return word.startsWith('ge') && word.endsWith('en') && !exceptions.includes(word) 10 | } 11 | 12 | export function unwrap (word, unsafe) { 13 | if (unsafe || test(word)) { 14 | return word.slice(2, word.length - 2) 15 | } 16 | } 17 | 18 | export function translate (word, translator) { 19 | if (test (word)) { 20 | return "past participle form of " + translator(unwrap(word, true)) 21 | } 22 | } 23 | 24 | export function annotate (word) { 25 | if (test (word)) { 26 | return "past participle / erminative aktionsart form of " + unwrap(word, true) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /vanilla/dict-viewer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
Original DeDictionary FormTranslations
EnRu
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /vanilla/scripts/ya.translation.js: -------------------------------------------------------------------------------- 1 | // NOT USED AT THE MOMENT 2 | 3 | const YANDEX_TRANSLATE_API_KEY = "YOUR KEY HERE" 4 | 5 | const requestYandexTranslation = () => { 6 | const urlEn = `https://translate.yandex.net/api/v1.5/tr.json/translate?key=${YANDEX_TRANSLATE_API_KEY}&lang=de-en&format=plain&text=${selectedWord}` 7 | fetch(urlEn).then(response => response.json()).then(data => { 8 | if (data.code !== 200) { 9 | console.error(data) 10 | return 11 | } 12 | translationEn = data.text[0] 13 | updateInfo() 14 | }) 15 | const urlRu = `https://translate.yandex.net/api/v1.5/tr.json/translate?key=${YANDEX_TRANSLATE_API_KEY}&lang=de-ru&format=plain&text=${selectedWord}` 16 | fetch(urlRu).then(response => response.json()).then(data => { 17 | if (data.code !== 200) { 18 | console.error(data) 19 | return 20 | } 21 | translationRu = data.text[0] 22 | updateInfo() 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /specific-language-tools/german/morphology/affixes/prefixes/ab*.js: -------------------------------------------------------------------------------- 1 | // see https://en.wikipedia.org/wiki/German_verbs#German_prefixes 2 | 3 | const prefix = 'ab' 4 | const prefixLength = prefix.length 5 | const prefixTranslation = prefix 6 | const prefixMeaning = prefix 7 | 8 | export const exceptions = [ 9 | ] 10 | 11 | export function test (word) { 12 | word = word.toLowerCase() 13 | return word.startsWith(prefix) && !exceptions.includes(word) 14 | } 15 | 16 | export function unwrap (word, unsafe) { 17 | if (unsafe || test(word)) { 18 | return word.slice(prefixLength, word.length) 19 | } 20 | } 21 | 22 | export function translate (word, translator) { 23 | if (test (word)) { 24 | return prefixTranslation + " + " + translator(unwrap(word, true)) 25 | } 26 | } 27 | 28 | export function annotate (word) { 29 | if (test (word)) { 30 | return prefixMeaning + " + " + unwrap(word, true) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /vanilla/reader-alternative.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Personal dictionary: 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /specific-language-tools/german/morphology/affixes/circumfixes/ver*en.js: -------------------------------------------------------------------------------- 1 | // see https://en.wiktionary.org/wiki/ver-_-en 2 | // see https://en.wiktionary.org/wiki/Category:German_words_circumfixed_with_ver-_-en 3 | 4 | export const exceptions = [ 5 | ] 6 | 7 | export const subsets = [ 8 | { 9 | type: 'circumfix', 10 | name: 'ver*fachen', 11 | }, 12 | ] 13 | 14 | export function test (word) { 15 | word = word.toLowerCase() 16 | return word.startsWith('ver') && word.endsWith('en') && !exceptions.includes(word) 17 | } 18 | 19 | export function unwrap (word, unsafe) { 20 | if (unsafe || test(word)) { 21 | return word.slice(3, word.length - 2) 22 | } 23 | } 24 | 25 | export function translate (word, translator) { 26 | if (test (word)) { 27 | return "make more/into " + translator(unwrap(word, true)) 28 | } 29 | } 30 | 31 | 32 | export function annotate (word) { 33 | if (test (word)) { 34 | return "make more/into " + unwrap(word, true) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /specific-language-tools/german/morphology/affixes/prefixes/ge*.js: -------------------------------------------------------------------------------- 1 | // see https://en.wiktionary.org/wiki/ge-#German 2 | 3 | export const subsets = [ 4 | { 5 | type: 'circumfix', 6 | name: 'ge*t', 7 | }, 8 | { 9 | type: 'circumfix', 10 | name: 'ge*en', 11 | } 12 | ] 13 | 14 | export const exceptions = [ 15 | 'gehen' 16 | ] 17 | 18 | export function test (word) { 19 | word = word.toLowerCase() 20 | return word.startsWith('ge') && !exceptions.includes(word) 21 | } 22 | 23 | export function unwrap (word, unsafe) { 24 | if (unsafe || test(word)) { 25 | return word.slice(2, word.length) 26 | } 27 | } 28 | 29 | export function translate (word, translator) { 30 | if (test (word)) { 31 | return "collective/action/verbal/accosication noun formed from " + translator(unwrap(word, true)) 32 | } 33 | } 34 | 35 | export function annotate (word) { 36 | if (test (word)) { 37 | return "collective/action/verbal/accosication noun formed from " + unwrap(word, true) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /vanilla/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |

Dolmetsch

16 | 17 |

Dolmetsch is a tool designed to help language learners with expanding their vocabularies.

18 | 19 | Read more at project's GitHub repository.

20 |
21 | 22 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /vanilla/styles/layout.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | padding: 0; 4 | font-size: 25px; 5 | } 6 | 7 | .primary-navigation { 8 | font-size: 0.8em; 9 | position: fixed; 10 | right: 0; 11 | top: 3em; 12 | border: 1px solid lightgray; 13 | z-index: 1; 14 | background: white; 15 | box-shadow: rgba(0,0,0,0.25) 5px 5px 20px; 16 | text-align: right; 17 | } 18 | .primary-navigation > * { 19 | padding: 0 1rem; 20 | } 21 | 22 | .primary-navigation > a > h2 { 23 | padding: .25em 0; 24 | text-align: right; 25 | margin: 0; 26 | } 27 | 28 | .primary-navigation > h4 { 29 | padding: .25em 1rem; 30 | text-align: right; 31 | margin: 0; 32 | } 33 | 34 | .primary-navigation > a { 35 | display: block; 36 | padding: .25em 1rem; 37 | } 38 | 39 | [contenteditable]:empty:before{ 40 | content: attr(placeholder); 41 | pointer-events: none; 42 | display: block; 43 | color: lightgray; 44 | } 45 | 46 | #current-text-language-selector { 47 | position: fixed; 48 | right: 0; 49 | bottom: 0; 50 | z-index: 1; 51 | } -------------------------------------------------------------------------------- /vanilla/scripts/layout.js: -------------------------------------------------------------------------------- 1 | import languagesAvailable from './languagesAvailable.js' 2 | 3 | const primaryNavigationHTML = ` 4 |

Dolmetsch

5 |
6 | about 7 | GitHub 8 |
9 |

Reader

10 | demo text 11 | own text 12 |
13 |

Dict

14 | view/edit 15 | export 16 | ` 17 | /* review */ 18 | 19 | document.getElementById('primary-navigation').innerHTML = primaryNavigationHTML 20 | 21 | 22 | const availableLanguagesHTML = languagesAvailable.reduce( 23 | (acc, { code, name }) => acc + ``, 24 | '' 25 | ) 26 | 27 | const $currentTextLanguageSelector = document.getElementById('current-text-language-selector') 28 | if ($currentTextLanguageSelector) { 29 | $currentTextLanguageSelector.innerHTML = availableLanguagesHTML 30 | $currentTextLanguageSelector.value = 'de' 31 | } 32 | -------------------------------------------------------------------------------- /specific-language-tools/german/morphology/affixes/test.js: -------------------------------------------------------------------------------- 1 | import { test } from './index.js' 2 | 3 | const words = [ 4 | 'verbessern', 5 | 'verbrennen', 6 | 7 | "urknall", "verbanden", "namens", "entstanden", "ereignis", "grundlegenden", "eigenschaften", "wiederum", "zusammenschlossen", "bestimmte", "bezeichnen", "aufbau", "entwicklung", "geprägt", "beschleunigte", "mitlebewesen", "menschenähnlichen", "stachen", "vielzahl", "vermutlich", "äußerlich", "gewisse", "matsch", "geräusch", "dreinblickenden", "werkzeugherstellung", "abgeschaut", "knurrend", "gefletschten", "vormachtstellung", "währenddessen", "trubel", "streift", "stritten", "nachwuchs", "zogen", "erfanden", "ahnen", "spalten", "unauffällige", "libellen", "quallen", "lebewesen", "fortpflanzungsfähige", "vorfahren", "fortpflanzung", "maultiere", "unfruchtbar", "erheblich", "zeugen", "gattungen", "gattung", "zweiteilige", "art", "urahn", "zahmsten", "katzenvorfahren", "scheinbar", "bestgehüteten", "waisenkind", "vettern", "krawalligen", "menschenaffen", "verwandten", "allernächsten", "urahnin", "geschichte", "menschheit", "unauffälliges" 8 | ] 9 | 10 | for (let word of words) { 11 | console.log(test(word)) 12 | } 13 | -------------------------------------------------------------------------------- /vanilla/scripts/sandbox/components/LiveTimer.js: -------------------------------------------------------------------------------- 1 | import TimeFormatted from "./TimeFormatted.js" 2 | 3 | export class LiveTimer extends HTMLElement { 4 | 5 | render () { 6 | if (!this._rendered) { 7 | this.innerHTML = `` 8 | this._rendered = true; 9 | } 10 | } 11 | 12 | _tickHandle 13 | _rendered = false 14 | _now 15 | 16 | connectedCallback () { 17 | this._now = new Date() 18 | this._tickHandle = setInterval(this.emitTick.bind(this), 1000) 19 | this.render(); 20 | } 21 | 22 | static get observedAttributes () { 23 | return []; 24 | } 25 | 26 | emitTick () { 27 | this._now = new Date() 28 | const event = new Event("tick"); 29 | event.details = this._now.getTime() 30 | this._rendered = false; 31 | this.render(); 32 | this.dispatchEvent(event); 33 | } 34 | 35 | attributeChangedCallback (name, oldValue, newValue) { 36 | this._rendered = false; 37 | this.render(); 38 | } 39 | 40 | } 41 | 42 | try { 43 | customElements.define('live-timer', LiveTimer) 44 | } catch (DOMException) { 45 | // pass 46 | } 47 | 48 | export default LiveTimer 49 | -------------------------------------------------------------------------------- /vanilla/scripts/reader/initText.js: -------------------------------------------------------------------------------- 1 | export const isPunctuationMark = (char) => ".?!,:;".includes(char) 2 | 3 | export const separatePunctuationReducer = (acc, word) => { 4 | if (!word || !word.length) { 5 | return acc 6 | } 7 | const lastCharIndex = word.length - 1 8 | const lastChar = word[lastCharIndex] 9 | if (isPunctuationMark(lastChar)) { 10 | acc.push(word.slice(0, lastCharIndex)) 11 | acc.push(lastChar) 12 | } else { 13 | acc.push(word) 14 | } 15 | return acc 16 | } 17 | 18 | export const isNumber = (word) => word && word.match(/[+-]?\d+([\.|\,]\d+)?/g)?.length === 1 19 | 20 | export const wrapIntoTag = (w) => { 21 | const className = isPunctuationMark(w) 22 | ? 'punctuation-mark' 23 | : isNumber(w) 24 | ? 'number' 25 | : 'word' 26 | return `${w}` 27 | } 28 | 29 | export const initText = ($text, text) => { 30 | $text.innerHTML = text.split('\n').reduce((acc, p) => { 31 | const s = p.split(' ').reduce(separatePunctuationReducer, []).map(wrapIntoTag).join('') 32 | acc += `

${s}

` 33 | return acc 34 | }, '') 35 | } 36 | 37 | export default initText 38 | -------------------------------------------------------------------------------- /vanilla/scripts/sandbox/components/TimeFormatted.js: -------------------------------------------------------------------------------- 1 | export class TimeFormatted extends HTMLElement { 2 | 3 | render () { 4 | let date = new Date(this.getAttribute('datetime') || Date.now()); 5 | 6 | this.innerHTML = new Intl.DateTimeFormat("default", { 7 | year: this.getAttribute('year') || undefined, 8 | month: this.getAttribute('month') || undefined, 9 | day: this.getAttribute('day') || undefined, 10 | hour: this.getAttribute('hour') || undefined, 11 | minute: this.getAttribute('minute') || undefined, 12 | second: this.getAttribute('second') || undefined, 13 | timeZoneName: this.getAttribute('time-zone-name') || undefined, 14 | }).format(date); 15 | } 16 | 17 | connectedCallback () { 18 | if (!this.rendered) { 19 | this.render(); 20 | this.rendered = true; 21 | } 22 | } 23 | 24 | static get observedAttributes () { 25 | return ['datetime', 'year', 'month', 'day', 'hour', 'minute', 'second', 'time-zone-name']; 26 | } 27 | 28 | attributeChangedCallback (name, oldValue, newValue) { 29 | this.render(); 30 | } 31 | 32 | } 33 | 34 | try { 35 | customElements.define("time-formatted", TimeFormatted) 36 | } catch (DOMException) { 37 | // pass 38 | } 39 | 40 | export default TimeFormatted 41 | -------------------------------------------------------------------------------- /specific-language-tools/german/morphology/affixes/infixes/index.js: -------------------------------------------------------------------------------- 1 | // see https://en.wiktionary.org/wiki/Category:German_infixes 2 | 3 | function generateInfixHandler (infix) { 4 | // const infixLength = infix.length 5 | const infixTranslation = infix 6 | const infixMeaning = infix 7 | 8 | const exceptions = [ 9 | ] 10 | 11 | function test (word) { 12 | word = word.toLowerCase() 13 | return word.includes(infix) 14 | && !word.startsWith(infix) && !word.endsWith(infix) 15 | && !exceptions.includes(word) 16 | } 17 | 18 | function unwrap (word, unsafe) { 19 | if (unsafe || test(word)) { 20 | return word 21 | } 22 | } 23 | 24 | function translate (word, translator) { 25 | if (test (word)) { 26 | return translator(unwrap(word, true)) + " + " + infixTranslation 27 | } 28 | } 29 | 30 | function annotate (word) { 31 | if (test (word)) { 32 | return unwrap(word, true) + " + " + infixMeaning 33 | } 34 | } 35 | 36 | return { 37 | test, 38 | unwrap, 39 | translate, 40 | annotate, 41 | affix: infix, 42 | affixType: 'infix', 43 | exceptions, 44 | } 45 | } 46 | 47 | export const infixes = [ 48 | "zu", 49 | ] 50 | 51 | export const handlers = infixes.map(generateInfixHandler) 52 | export default handlers -------------------------------------------------------------------------------- /vanilla/scripts/dict-export.js: -------------------------------------------------------------------------------- 1 | import { getDict } from './dict.js' 2 | import languagesAvailable from './languagesAvailable.js' 3 | 4 | // let currentLanguage = null 5 | const $de = document.getElementById('dict-export') 6 | 7 | const renderExport = () => { 8 | let stringified = "{\n"; 9 | for (let cl of languagesAvailable) { 10 | stringified += '\n"' + cl.code + '": {\n' 11 | for (let targetLanguage of ['ru', 'en', 'dictionaryForm']) { 12 | const dict = getDict(cl.code, targetLanguage) 13 | stringified += '"' + targetLanguage + '": ' + JSON.stringify(dict, Object.keys(dict).sort(), 2) + ',\n' 14 | } 15 | if (stringified.length > 2) { 16 | stringified = stringified.slice(0, stringified.length - 2) + "\n" 17 | } 18 | stringified += "},\n" 19 | } 20 | if (stringified.length > 2) { 21 | stringified = stringified.slice(0, stringified.length - 2) + "\n" 22 | } 23 | $de.innerText = stringified + "}" 24 | } 25 | 26 | // const $currentTextlanguageSelector = document.getElementById('current-text-language-selector') 27 | // if ($currentTextlanguageSelector) { 28 | // $currentTextlanguageSelector.addEventListener('input', () => { 29 | // currentLanguage = $currentTextlanguageSelector.value 30 | // renderExport() 31 | // }) 32 | // } 33 | 34 | renderExport() 35 | -------------------------------------------------------------------------------- /vanilla/reader-demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Personal dictionary: 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 |
24 |
25 |

26 | 	
27 | 28 | 32 | 33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /specific-language-tools/german/morphology/affixes/interfixes/index.js: -------------------------------------------------------------------------------- 1 | // see https://en.wiktionary.org/wiki/Category:German_interfixes 2 | 3 | function generateInterfixHandler (interfix) { 4 | // const interfixLength = interfix.length 5 | const interfixTranslation = interfix 6 | const interfixMeaning = interfix 7 | 8 | const exceptions = [ 9 | ] 10 | 11 | function test (word) { 12 | word = word.toLowerCase() 13 | return word.includes(interfix) 14 | && !word.startsWith(interfix) && !word.endsWith(interfix) 15 | && !exceptions.includes(word) 16 | } 17 | 18 | function unwrap (word, unsafe) { 19 | if (unsafe || test(word)) { 20 | return word 21 | } 22 | } 23 | 24 | function translate (word, translator) { 25 | if (test (word)) { 26 | return translator(unwrap(word, true)) + " + " + interfixTranslation 27 | } 28 | } 29 | 30 | function annotate (word) { 31 | if (test (word)) { 32 | return unwrap(word, true) + " + " + interfixMeaning 33 | } 34 | } 35 | 36 | return { 37 | test, 38 | unwrap, 39 | translate, 40 | annotate, 41 | exceptions, 42 | affix: interfix, 43 | affixType: 'interfix', 44 | } 45 | } 46 | 47 | export const interfixes = [ 48 | "e", 49 | "en", 50 | "ens", 51 | "er", 52 | "es", 53 | 54 | "n", 55 | "ns", 56 | 57 | "o", 58 | 59 | "s", 60 | ] 61 | 62 | export const handlers = interfixes.map(generateInterfixHandler) 63 | export default handlers -------------------------------------------------------------------------------- /vanilla/scripts/sandbox/components/reader-text.css: -------------------------------------------------------------------------------- 1 | #text { 2 | max-width: 800px; 3 | margin: 2em auto; 4 | /* font-size: 0; */ 5 | } 6 | 7 | #text .paragraph.selected { 8 | /* border-color: #90ee9055; */ 9 | background-color: #90ee9022; 10 | } 11 | 12 | #text .sentence.selected { 13 | /* border-color: #90ee90aa; */ 14 | background-color: #90ee9044; 15 | } 16 | 17 | #text .paragraph, 18 | #text .sentence, 19 | #text .word { 20 | /* border: 1px solid transparent; */ 21 | border-radius: .3em; 22 | transition: all ease-in-out .1s; 23 | } 24 | 25 | #text .word { 26 | /* font-size: 1rem; */ 27 | display: inline; /* just inline for new reader, to allow text selection */ 28 | margin-right: .3em; 29 | cursor: pointer; 30 | /* word-break: keep-all; */ 31 | } 32 | /* #text *.selected { 33 | background-color: #90ee9015; 34 | } */ 35 | 36 | #text .whitespaces { 37 | font-size: 0; 38 | } 39 | 40 | #text .word:hover > .word-proper { 41 | /* border-color: black; */ 42 | background-color: goldenrod; 43 | } 44 | #text .word.selected > .word-proper, 45 | #text .word.occurence.selected > .word-proper { 46 | background-color: #90ee90; 47 | } 48 | #text .word.occurence > .word-proper { 49 | background-color: #90ee9044; 50 | } 51 | #text .word-proper.translated { 52 | text-decoration: underline; 53 | text-decoration-style: dashed; 54 | text-decoration-color: lightgray; 55 | } 56 | -------------------------------------------------------------------------------- /specific-language-tools/german/morphology/affixes/prefixes/index.js: -------------------------------------------------------------------------------- 1 | // see https://en.wikipedia.org/wiki/German_verbs#German_prefixes 2 | 3 | function generatePrefixHandler (prefix) { 4 | const prefixLength = prefix.length 5 | const prefixTranslation = prefix 6 | const prefixMeaning = prefix 7 | 8 | const exceptions = [ 9 | ] 10 | 11 | function test (word) { 12 | word = word.toLowerCase() 13 | return word.startsWith(prefix) && !exceptions.includes(word) 14 | } 15 | 16 | function unwrap (word, unsafe) { 17 | if (unsafe || test(word)) { 18 | return word.slice(prefixLength, word.length) 19 | } 20 | } 21 | 22 | function translate (word, translator) { 23 | if (test (word)) { 24 | return prefixTranslation + " + " + translator(unwrap(word, true)) 25 | } 26 | } 27 | 28 | function annotate (word) { 29 | if (test (word)) { 30 | return prefixMeaning + " + " + unwrap(word, true) 31 | } 32 | } 33 | 34 | return { 35 | test, 36 | unwrap, 37 | translate, 38 | annotate, 39 | exceptions, 40 | affix: prefix, 41 | affixType: 'prefix', 42 | } 43 | } 44 | 45 | export const prefixes = [ 46 | 'ab', 47 | 'an', 48 | 'auf', 49 | 'aus', 50 | 'be', 51 | 'bei', 52 | 'dar', 53 | 'ein', 54 | 'ent', 55 | 'emp', 56 | 'er', 57 | 'ge', 58 | 'hin', 59 | 'her', 60 | 'nach', 61 | 'nieder', 62 | 'über', 63 | 'um', 64 | 'unter', 65 | 'ver', 66 | 'vor', 67 | 'weg', 68 | 'wider', 69 | 'zer', 70 | 'zu', 71 | 'zusammen', 72 | ] 73 | 74 | export const handlers = prefixes.map(generatePrefixHandler) 75 | export default handlers -------------------------------------------------------------------------------- /vanilla/reader-own-text.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Personal dictionary: 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 |
24 |
25 |

26 | 	
27 | 28 | 30 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /vanilla/scripts/reader-alternative/text-parsing.js: -------------------------------------------------------------------------------- 1 | export const punctuation = ".?!,:;()\n" 2 | export const sentenceDelimiters = ['. ', '? ', '! ', '.\n', '?\n', '!\n', '«, '] 3 | 4 | export function* gVariativeSplit (text, separators) { 5 | if (!separators) { 6 | yield text 7 | return 8 | } 9 | if (!separators.length) { 10 | for (let sample in gVariativeSplit(text, [separators])) { 11 | yield sample 12 | } 13 | return 14 | } 15 | let sample = '' 16 | let newChunkStartIndex = 0 17 | let i = 0 18 | for (i = 0; i < text.length; i++) { 19 | const char = text[i] 20 | sample += char 21 | if (separators.find(separator => sample.endsWith(separator))) { 22 | yield text.slice(newChunkStartIndex, i + 1) 23 | sample = '' 24 | newChunkStartIndex = i + 1 25 | } 26 | } 27 | if (newChunkStartIndex < i) { 28 | yield text.slice(newChunkStartIndex) 29 | } 30 | } 31 | 32 | export function* gParseRawTextParagraphs (rawText) { 33 | for (let paragraph of rawText.split('\n').filter(s => s.length > 0)) { 34 | yield paragraph 35 | } 36 | } 37 | 38 | export function* gParseRawTextSentences (rawText) { 39 | for (let paragraph of [ 40 | ...gVariativeSplit(rawText, sentenceDelimiters 41 | )].filter(s => s.length > 0)) { 42 | yield paragraph 43 | } 44 | } 45 | 46 | export function* gParseRawTextWords (sentence) { 47 | for (let word of [...gVariativeSplit(sentence, [' ',])]) { 48 | yield word 49 | } 50 | } 51 | 52 | export function* gParseRawText (rawText) { 53 | for (let paragraph of gParseRawTextParagraphs(rawText)) { 54 | const p = [] 55 | for (let sentence of gParseRawTextSentences(paragraph)) { 56 | p.push([...gParseRawTextWords(sentence)]) 57 | } 58 | yield p 59 | } 60 | } 61 | 62 | 63 | export default gParseRawText 64 | -------------------------------------------------------------------------------- /vanilla/scripts/dict-viewer.js: -------------------------------------------------------------------------------- 1 | import { getTranslation, setTranslation, getKeys } from './dict.js' 2 | 3 | const $dictTBody = document.getElementById('dictTBody') 4 | 5 | let currentLanguage = 'de' 6 | 7 | const renderDict = () => { 8 | const keys = getKeys(currentLanguage) 9 | $dictTBody.innerHTML = keys.map(k => ` 10 | ${k} 11 | ${ getTranslation(currentLanguage, 'dictionaryForm', k) || ''} 12 | ${ getTranslation(currentLanguage, 'ru', k) || ''} 13 | ${ getTranslation(currentLanguage, 'en', k) || ''} 14 | `).join('') 15 | } 16 | 17 | document.body.addEventListener('keyup', (e) => { 18 | if (e.target.classList.contains('translation')) { 19 | const selectedWord = e.target.parentNode.querySelector('td:first-child').innerText 20 | const translationText = e.target.innerText 21 | if (e.target.classList.contains('translation-ru')) { 22 | setTranslation(currentLanguage, 'ru', selectedWord, translationText) 23 | } 24 | if (e.target.classList.contains('translation-en')) { 25 | setTranslation(currentLanguage, 'en', selectedWord, translationText) 26 | } 27 | if (e.target.classList.contains('dictionary-form')) { 28 | setTranslation(currentLanguage, 'dictionaryForm', selectedWord, translationText) 29 | } 30 | } 31 | }, true) 32 | 33 | renderDict() 34 | 35 | const $currentTextlanguageSelector = document.getElementById('current-text-language-selector') 36 | if ($currentTextlanguageSelector) { 37 | $currentTextlanguageSelector.addEventListener('input', () => { 38 | currentLanguage = $currentTextlanguageSelector.value 39 | renderDict() 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dolmetsch 2 | 3 | Just a developer-friendly tool for reading texts in foreign languages and improving reader's vocabulary. 4 | 5 | ## Alpha Version Disclaimer 6 | 7 | It's still quite an early alpha, more like a proof of concept and some raw material for others to experiment on. 8 | But if you are a web developer and happen to be learning some foreign languages, you may find it quite useful already. 9 | 10 | Live demo is available at http://dolmetsch.gbezyuk.ru 11 | 12 | ![Live demo screenshot](/screenshot.png?raw=true "Live demo screenshot") 13 | 14 | 15 | ## Current State of Affairs 16 | 17 | There's currently a just a simple vanilla HTML/CSS/JS draft implementation. 18 | 19 | It requires only a static HTTP server to run (node-based `http-server` would do locally) and supports: 20 | * parsing a piece of text into words, handling punctuation and numbers separately 21 | * reader mode, where user can click on the words to see/edit translations 22 | * vocabulary mode, where translations can be viewed all at once and edited if necessary 23 | * vocabulary export mode (JSON) 24 | * localStorage persistency 25 | 26 | In order to illustrate the approach, I use first two pages of the German translation of the amazing 27 | "Sapiens" by truly brilliant Yuval Noah Harari. Translation target languages are English and Russian. 28 | 29 | ## Known Issues and Development Plans 30 | * [ ] Fullstops layout is buggy and to be fixed. It may require changing the layout approach. 31 | * [ ] Words are now considered "as is" (all lowercase, though), showing no care for inflections. Thus, caring more about grammar is an obvious field for improvement. 32 | * [ ] Speaking of grammar, one particularly interesting case would be German detachable prefixes: "aussagen -> ich sage aus". 33 | * [ ] It's not enough just to record the words into a personal vocabulary. One needs to do reviews and test oneself. So, tools of this sort are also coming. 34 | * [ ] Audio. Quite a separate topic. Some YouTube etc. integration also lurks in this corner. 35 | * [ ] Support for other languages. 36 | * [ ] Generally speaking, product-quality. 37 | -------------------------------------------------------------------------------- /specific-language-tools/german/morphology/affixes/index.js: -------------------------------------------------------------------------------- 1 | import circumfixes from './circumfixes/index.js' 2 | import suffixes from './suffixes/index.js' 3 | import prefixes from './prefixes/index.js' 4 | // import infixes from './infixes/index.js' 5 | // import interfixes from './interfixes/index.js' 6 | 7 | export const affixes = [ 8 | ...circumfixes, 9 | ...suffixes, 10 | ...prefixes, 11 | // ...infixes, 12 | // ...interfixes, 13 | ] 14 | 15 | function getMask (affixInfo) { 16 | switch (affixInfo.affixType) { 17 | case 'prefix': 18 | return affixInfo.affix + '*' 19 | case 'suffix': 20 | return '*' + affixInfo.affix 21 | case 'circumfix': 22 | return affixInfo.affix 23 | } 24 | } 25 | 26 | function analyze (matches) { 27 | // just a very quick demo of the analytical approach 28 | // one obvious application would be figuring out infinitives 29 | const possibilities = [] 30 | let m 31 | if (m = matches.find(m => m.affixType === 'suffix' && m.affix === 'e')) { 32 | possibilities.push(['*e adjective plural or feminine form', m.unwrapped.toLowerCase()]) 33 | possibilities.push('*e noun plural or feminine form') 34 | } 35 | if (m = matches.find(m => m.affixType === 'suffix' && m.affix === 'en')) { 36 | possibilities.push('*en verb infinitive or plural form') 37 | possibilities.push('*en adjective in accusative') 38 | possibilities.push(['*en noun plural', m.unwrapped]) 39 | } 40 | if (m = matches.find(m => m.affixType === 'suffix' && m.affix === 'heit')) { 41 | possibilities.push('*heit noun') 42 | } 43 | if (m = matches.find(m => m.affixType === 'suffix' && m.affix === 'bar')) { 44 | possibilities.push('*bar adjective') 45 | } 46 | if (m = matches.find(m => m.affixType === 'suffix' && m.affix === 'lich')) { 47 | possibilities.push('*lich adverb') 48 | } 49 | if (m = matches.find(m => m.affixType === 'suffix' && m.affix === 'es')) { 50 | possibilities.push(['*es adjective (in neutrum definite form)', m.unwrapped.toLowerCase()]) 51 | } 52 | return possibilities 53 | } 54 | 55 | export function test (word) { 56 | const matches = [] 57 | for (let a of affixes) { 58 | if (a.test(word)) { 59 | matches.push({ 60 | affix: a.affix, 61 | affixMask: getMask(a), 62 | affixType: a.affixType, 63 | annotation: a.annotate(word), 64 | unwrapped: a.unwrap(word), 65 | }) 66 | } 67 | } 68 | return { 69 | word, 70 | matches, 71 | analysis: analyze(matches) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /vanilla/styles/reader.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 4em; 3 | } 4 | 5 | .note { 6 | font-size: 0.5rem; 7 | max-width: 800px; 8 | margin: 4em auto; 9 | } 10 | 11 | textarea { 12 | width: 800px; 13 | font-size: 0.8em; 14 | display: block; 15 | margin: 0 auto; 16 | } 17 | 18 | button { 19 | width: 800px; 20 | font-size: 1.5em; 21 | cursor: pointer; 22 | display: block; 23 | margin: 0 auto; 24 | } 25 | 26 | #text { 27 | max-width: 800px; 28 | margin: 2em auto; 29 | user-select: none; 30 | } 31 | .word { 32 | display: inline-block; 33 | margin-right: .3em; 34 | border: 1px solid transparent; 35 | border-radius: .3em; 36 | cursor: pointer; 37 | transition: all ease-in-out .1s; 38 | } 39 | .word:hover { 40 | border-color: black; 41 | } 42 | .word.selected { 43 | background-color: lightgreen; 44 | } 45 | .word.translated { 46 | text-decoration: underline; 47 | text-decoration-style: dashed; 48 | text-decoration-color: lightgray; 49 | } 50 | .punctuation-mark { 51 | display: inline-block; 52 | margin-right: .3em; 53 | margin-left: -.4em; 54 | } 55 | 56 | .number { 57 | display: inline-block; 58 | margin-right: .3em; 59 | } 60 | .number:hover { 61 | text-decoration: underline; 62 | } 63 | 64 | #info { 65 | position: fixed; 66 | background: white; 67 | box-shadow: rgba(0,0,0,0.25) 5px 5px 20px; 68 | left: 0; 69 | right: 0; 70 | top: 0; 71 | text-align: center; 72 | padding: 0.5em; 73 | } 74 | #info > * { 75 | display: inline-block; 76 | margin-right: 3em; 77 | } 78 | 79 | #info > .title { 80 | font-size: 0.5rem; 81 | color: lightgray; 82 | } 83 | 84 | #info #occurences { 85 | color: lightgray; 86 | } 87 | 88 | #info .translation { 89 | display: inline-block; 90 | min-width: 3em; 91 | border-bottom: 1px solid lightgray; 92 | white-space: nowrap; 93 | } 94 | #info > .translation > br { 95 | display: none; 96 | } 97 | 98 | .remote-dict-info { 99 | position: fixed; 100 | left: -300px; 101 | top: 5em; 102 | bottom: 5em; 103 | width: 300px; 104 | background: white; 105 | font-size: 0.5em; 106 | box-shadow: rgba(0,0,0,0.25) 5px 5px 20px; 107 | } 108 | .remote-dict-info.visible { 109 | left: 0; 110 | } 111 | .remote-dict-info.visible > .ear { 112 | right: 0; 113 | } 114 | 115 | .remote-dict-info > .ear { 116 | position: absolute; 117 | top: 50%; 118 | right: -25px; 119 | margin-top: -25px; 120 | height: 50px; 121 | width: 25px; 122 | cursor: pointer; 123 | } -------------------------------------------------------------------------------- /vanilla/scripts/sandbox/components/ReaderText.js: -------------------------------------------------------------------------------- 1 | import { fillTextNode, initReader } from "../../reader-alternative/dom-interaction.js" 2 | import { isTranslated } from '../../dict.js' 3 | 4 | const templateHTML = ` 5 | 6 |
7 | ` 8 | 9 | export class ReaderText extends HTMLElement { 10 | 11 | _rendered = false 12 | 13 | render () { 14 | if (!this._rendered) { 15 | fillTextNode(this._shadowTree.getElementById('text'), this.getAttribute('raw-text') || "") 16 | this._renderDictPresenceStatus() 17 | this._rendered = true; 18 | } 19 | } 20 | 21 | _selectionState 22 | _shadowTree 23 | _currentLanguage 24 | 25 | connectedCallback () { 26 | this._shadowTree = this.attachShadow({mode: 'open'}); 27 | // this._shadowTree.append(document.getElementById('reader-text-template').content.cloneNode(true)); 28 | this._shadowTree.innerHTML = templateHTML 29 | this._selectionState = initReader(this._shadowTree.getElementById('text'), this._onSelectionStateChange.bind(this)) 30 | this._currentLanguage = this.getAttribute('original-language') || 'de' 31 | this.render(); 32 | 33 | this.addEventListener('vocab-updated', this._onVocabUpdated.bind(this)) 34 | } 35 | 36 | static get observedAttributes () { 37 | return [ 'raw-text', 'original-language' ]; 38 | } 39 | 40 | attributeChangedCallback (name, oldValue, newValue) { 41 | switch (name) { 42 | case 'raw-text': 43 | if (this._rendered) { 44 | this._rendered = false; 45 | this.render(); 46 | } 47 | break 48 | case 'original-language': 49 | this._currentLanguage = newValue 50 | if (this._rendered) { 51 | this._renderDictPresenceStatus() 52 | } 53 | break 54 | } 55 | } 56 | 57 | _onSelectionStateChange (selectionState) { 58 | this._selectionState = selectionState 59 | const event = new Event("selection-change"); 60 | event.details = { 61 | wordRaw: selectionState.selectedWord, 62 | wordSanitized: selectionState.selectedWordSanitized, 63 | } 64 | this.dispatchEvent(event); 65 | } 66 | 67 | _onVocabUpdated (e) { 68 | e.stopPropagation() 69 | this._renderDictPresenceStatus() 70 | } 71 | 72 | _renderDictPresenceStatus () { 73 | [...this._shadowTree.getElementById('text').getElementsByClassName('word-proper')].map($w => { 74 | const w = $w.innerText.toLowerCase() 75 | $w.classList.toggle('translated', isTranslated(this._currentLanguage, w)) 76 | }) 77 | } 78 | 79 | } 80 | 81 | try { 82 | customElements.define('reader-text', ReaderText) 83 | } catch (DOMException) { 84 | // pass 85 | } 86 | 87 | export default ReaderText 88 | -------------------------------------------------------------------------------- /vanilla/scripts/reader-alternative.js: -------------------------------------------------------------------------------- 1 | import { initReader, fillTextNode } from "./reader-alternative/dom-interaction.js" 2 | import { getTranslation, isTranslated, setTranslation } from './dict.js' 3 | import demoText from './reader/text.js' 4 | 5 | const $text = document.getElementById('text') 6 | const targetLanguages = ['en', 'ru'] 7 | const currentLanguage = 'de' 8 | 9 | const renderDictPresenceStatus = () => { 10 | [...document.getElementsByClassName('word-proper')].map($w => { 11 | const w = $w.innerText.toLowerCase() 12 | $w.classList.toggle('translated', isTranslated(currentLanguage, w)) 13 | }) 14 | } 15 | 16 | function onSelectionStateChange (selectionState) { 17 | // console.log( 18 | // selectionState.selectedWordSanitized, 19 | // selectionState.selectedWord, 20 | // targetLanguages.map(tl => getTranslation(currentLanguage, tl, selectionState.selectedWordSanitized)) 21 | // ) 22 | updateInfo(selectionState) 23 | } 24 | 25 | const $originalWord = document.getElementById('originalWord') 26 | // const $dictionaryForm = document.getElementById('dictionaryForm') 27 | // const $occurences = document.getElementById('occurences') 28 | const $translationEn = document.getElementById('translationEn') 29 | const $translationRu = document.getElementById('translationRu') 30 | 31 | function updateInfo (selectionState) { 32 | const selectedWord = selectionState.selectedWord 33 | const dictionaryForm = getTranslation(currentLanguage, 'dictionaryForm', selectionState.selectedWordSanitized) 34 | const translationEn = getTranslation(currentLanguage, 'en', selectionState.selectedWordSanitized) 35 | const translationRu = getTranslation(currentLanguage, 'ru', selectionState.selectedWordSanitized) 36 | $originalWord.innerText = 37 | selectedWord === dictionaryForm || !dictionaryForm 38 | ? selectedWord 39 | : `${selectedWord} (${dictionaryForm || '?'})` 40 | // $dictionaryForm.innerText = dictionaryForm 41 | // $occurences.innerText = occurences 42 | $translationEn.innerText = translationEn || '' 43 | $translationRu.innerText = translationRu || '' 44 | } 45 | 46 | const selectionState = initReader($text, onSelectionStateChange) 47 | fillTextNode($text, demoText) 48 | renderDictPresenceStatus() 49 | 50 | document.body.addEventListener('keyup', (e) => { 51 | if (!selectionState.selectedWord) { 52 | return 53 | } 54 | if (e.target.classList.contains('translation')) { 55 | const translationText = e.target.innerText 56 | switch (e.target.id) { 57 | case 'translationRu': 58 | setTranslation(currentLanguage, 'ru', selectionState.selectedWord, translationText) 59 | break 60 | case 'translationEn': 61 | setTranslation(currentLanguage, 'en', selectionState.selectedWord, translationText) 62 | break 63 | } 64 | setTimeout(renderDictPresenceStatus, 0) 65 | } 66 | }, true) 67 | -------------------------------------------------------------------------------- /vanilla/styles/reader-alternative.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 4em; 3 | } 4 | 5 | #text { 6 | max-width: 800px; 7 | margin: 2em auto; 8 | /* font-size: 0; */ 9 | } 10 | 11 | #text .paragraph.selected { 12 | /* border-color: #90ee9055; */ 13 | background-color: #90ee9022; 14 | } 15 | 16 | #text .sentence.selected { 17 | /* border-color: #90ee90aa; */ 18 | background-color: #90ee9044; 19 | } 20 | 21 | #text .paragraph, 22 | #text .sentence, 23 | #text .word { 24 | /* border: 1px solid transparent; */ 25 | border-radius: .3em; 26 | transition: all ease-in-out .1s; 27 | } 28 | 29 | #text .word { 30 | /* font-size: 1rem; */ 31 | display: inline; /* just inline for new reader, to allow text selection */ 32 | margin-right: .3em; 33 | cursor: pointer; 34 | /* word-break: keep-all; */ 35 | } 36 | /* #text *.selected { 37 | background-color: #90ee9015; 38 | } */ 39 | 40 | #text .whitespaces { 41 | font-size: 0; 42 | } 43 | 44 | #text .word:hover > .word-proper { 45 | /* border-color: black; */ 46 | background-color: goldenrod; 47 | } 48 | #text .word.selected > .word-proper, 49 | #text .word.occurence.selected > .word-proper { 50 | background-color: #90ee90; 51 | } 52 | #text .word.occurence > .word-proper { 53 | background-color: #90ee9044; 54 | } 55 | #text .word-proper.translated { 56 | text-decoration: underline; 57 | text-decoration-style: dashed; 58 | text-decoration-color: lightgray; 59 | } 60 | .punctuation-mark { 61 | display: inline-block; 62 | margin-right: .3em; 63 | margin-left: -.4em; 64 | } 65 | 66 | .number { 67 | display: inline-block; 68 | margin-right: .3em; 69 | } 70 | .number:hover { 71 | text-decoration: underline; 72 | } 73 | 74 | #info { 75 | position: fixed; 76 | background: white; 77 | box-shadow: rgba(0,0,0,0.25) 5px 5px 20px; 78 | left: 0; 79 | right: 0; 80 | top: 0; 81 | text-align: center; 82 | padding: 0.5em; 83 | } 84 | #info > * { 85 | display: inline-block; 86 | margin-right: 3em; 87 | } 88 | 89 | #info > .title { 90 | font-size: 0.5rem; 91 | color: lightgray; 92 | } 93 | 94 | #info #occurences { 95 | color: lightgray; 96 | } 97 | 98 | #info .translation { 99 | display: inline-block; 100 | min-width: 3em; 101 | border-bottom: 1px solid lightgray; 102 | white-space: nowrap; 103 | } 104 | #info > .translation > br { 105 | display: none; 106 | } 107 | 108 | .remote-dict-info { 109 | position: fixed; 110 | left: -300px; 111 | top: 5em; 112 | bottom: 5em; 113 | width: 300px; 114 | background: white; 115 | font-size: 0.5em; 116 | box-shadow: rgba(0,0,0,0.25) 5px 5px 20px; 117 | } 118 | .remote-dict-info.visible { 119 | left: 0; 120 | } 121 | .remote-dict-info.visible > .ear { 122 | right: 0; 123 | } 124 | 125 | .remote-dict-info > .ear { 126 | position: absolute; 127 | top: 50%; 128 | right: -25px; 129 | margin-top: -25px; 130 | height: 50px; 131 | width: 25px; 132 | cursor: pointer; 133 | } -------------------------------------------------------------------------------- /vanilla/scripts/dict.js: -------------------------------------------------------------------------------- 1 | import germanHarariDict from "./german-tools/text-specific-dictionaries/harari.js" 2 | import defaultDutchDict from "./dutch-tools/defaultDict.js" 3 | import { languagesAvailable } from "./languagesAvailable.js" 4 | 5 | const LOCAL_STORAGE_KEY = 'translations' // TODO: key change 6 | 7 | const persistency = { 8 | get: () => JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)), 9 | store: () => localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(dicts)) 10 | } 11 | 12 | let dicts 13 | const translations = persistency.get() 14 | 15 | // migration 01 => 02 16 | if (translations && !('de' in translations)) { 17 | 18 | // migration 00 => 01 19 | if (!translations["dictionaryForm"]) { 20 | translations["dictionaryForm"] = {} 21 | } 22 | 23 | dicts = { 24 | de: translations 25 | } 26 | } else { 27 | dicts = translations || { 28 | de: germanHarariDict, 29 | nl: defaultDutchDict, 30 | } 31 | } 32 | 33 | for (let langCode of languagesAvailable.map(l => l.code)) { 34 | if (!(langCode in dicts)) { 35 | dicts[langCode] = { 36 | en: {}, 37 | ru: {}, 38 | dictionaryForm: {}, 39 | } 40 | } 41 | } 42 | 43 | export const getTranslation = (originalLanguage, targetLanguage, word) => { 44 | try { 45 | return dicts[originalLanguage][targetLanguage][word.toLowerCase()] 46 | } catch { 47 | return undefined 48 | } 49 | } 50 | 51 | export const isTranslated = (originalLanguage, word) => { 52 | if (!(originalLanguage in dicts)) { 53 | return false 54 | } 55 | for (let targetLanguage of Object.keys(dicts[originalLanguage])) { 56 | const dict = dicts[originalLanguage][targetLanguage] 57 | if (word in dict && !!dict[word]) { 58 | return true 59 | } 60 | } 61 | return false 62 | } 63 | 64 | export const setTranslation = (originalLanguage, targetLanguage, original, translation) => { 65 | if (!(originalLanguage in dicts)) { 66 | dicts[originalLanguage] = {} 67 | } 68 | if (!(targetLanguage in dicts[originalLanguage])) { 69 | dicts[originalLanguage][targetLanguage] = {} 70 | } 71 | dicts[originalLanguage][targetLanguage][original.toLocaleLowerCase()] = translation 72 | persistency.store() 73 | } 74 | 75 | export const requestLocalTranslation = (word) => { 76 | fetch(`/local-dictionary/${word.toLocaleLowerCase()}.json`) 77 | .then((response) => { 78 | if (response.ok) { 79 | return response.json() 80 | } else { 81 | throw new Error('Network response was not OK', response) 82 | } 83 | }) 84 | .then(console.log.bind(console)) 85 | .catch(response => console.log('no local dict entry', response)) 86 | } 87 | 88 | 89 | const _setUnion = (setA, setB) => { 90 | let _union = new Set(setA) 91 | for (let elem of setB) { 92 | _union.add(elem) 93 | } 94 | return _union 95 | } 96 | 97 | export const getKeys = (originalLanguage) => { 98 | if (! (originalLanguage in dicts)) { 99 | return [] 100 | } else { 101 | const dict = dicts[originalLanguage] 102 | const targetLanguages = Object.keys(dict) 103 | let keySet = new Set() 104 | for (let targetLanguage of targetLanguages) { 105 | keySet = _setUnion( 106 | keySet, 107 | new Set( 108 | Object.keys( 109 | dict[targetLanguage] 110 | ) 111 | ) 112 | ) 113 | } 114 | const keys = [...keySet] 115 | keys.sort() 116 | return keys 117 | } 118 | } 119 | 120 | 121 | export const getDict = (originalLanguage, targetLanguage) => { 122 | return dicts[originalLanguage] && dicts[originalLanguage][targetLanguage] 123 | } -------------------------------------------------------------------------------- /vanilla/scripts/sandbox/components/WordInfo.js: -------------------------------------------------------------------------------- 1 | import { setTranslation, getTranslation } from '../../dict.js' 2 | 3 | const templateHTML = ` 4 | 5 |
6 | Personal dictionary: 7 | 8 | 9 | 10 |
11 | ` 12 | 13 | export class WordInfo extends HTMLElement { 14 | 15 | _rendered = false 16 | 17 | render () { 18 | if (!this._rendered) { 19 | 20 | const dictionaryForm = getTranslation(this._originalLanguage, 'dictionaryForm', this._originalWordSanitized) 21 | const translationEn = getTranslation(this._originalLanguage, 'en', this._originalWordSanitized) 22 | const translationRu = getTranslation(this._originalLanguage, 'ru', this._originalWordSanitized) 23 | 24 | this._$originalWord.innerText = 25 | this._originalWord === dictionaryForm || !dictionaryForm 26 | ? this._originalWord 27 | : `${this._originalWord} (${dictionaryForm || '?'})` 28 | 29 | this._$translationEn.innerText = translationEn || '' 30 | this._$translationRu.innerText = translationRu || '' 31 | 32 | this._rendered = true; 33 | } 34 | } 35 | 36 | _shadowTree 37 | _originalLanguage 38 | _originalWord 39 | _originalWordSanitized 40 | 41 | connectedCallback () { 42 | this._shadowTree = this.attachShadow({mode: 'open'}); 43 | this._shadowTree.innerHTML = templateHTML 44 | 45 | this._$originalWord = this._shadowTree.getElementById('originalWord') 46 | this._$translationEn = this._shadowTree.getElementById('translationEn') 47 | this._$translationRu = this._shadowTree.getElementById('translationRu') 48 | 49 | this._originalLanguage = this.getAttribute('original-language') || 'de' 50 | this._originalWord = this.getAttribute('original-word') || '' 51 | this._originalWordSanitized = this.getAttribute('original-word-sanitizied') || '' 52 | 53 | this._shadowTree.addEventListener('keyup', this.onKeyUp.bind(this)) 54 | 55 | this.render(); 56 | } 57 | 58 | onKeyUp (e) { 59 | if (!this._originalWord) { 60 | e.preventDefault() 61 | return 62 | } 63 | if (e.target.classList.contains('translation')) { 64 | const translationText = e.target.innerText 65 | switch (e.target.id) { 66 | case 'translationRu': 67 | setTranslation(this._originalLanguage, 'ru', this._originalWordSanitized, translationText) 68 | break 69 | case 'translationEn': 70 | setTranslation(this._originalLanguage, 'en', this._originalWordSanitized, translationText) 71 | break 72 | } 73 | this.dispatchEvent(new Event("translation-updated")) 74 | } 75 | } 76 | 77 | static get observedAttributes () { 78 | return [ 'original-language', 'original-word', 'original-word-sanitized' ]; 79 | } 80 | 81 | attributeChangedCallback (name, oldValue, newValue) { 82 | switch (name) { 83 | case 'original-language': 84 | this._currentLanguage = newValue 85 | if (this._rendered) { 86 | this._rendered = false; 87 | this.render(); 88 | } 89 | break 90 | case 'original-word': 91 | this._originalWord = newValue 92 | if (this._rendered) { 93 | this._rendered = false; 94 | this.render(); 95 | } 96 | break 97 | case 'original-word-sanitized': 98 | this._originalWordSanitized = newValue 99 | if (this._rendered) { 100 | this._rendered = false; 101 | this.render(); 102 | } 103 | break 104 | } 105 | } 106 | 107 | } 108 | 109 | try { 110 | customElements.define('word-info', WordInfo) 111 | } catch (DOMException) { 112 | // pass 113 | } 114 | 115 | export default WordInfo 116 | -------------------------------------------------------------------------------- /specific-language-tools/german/morphology/affixes/suffixes/index.js: -------------------------------------------------------------------------------- 1 | // see https://en.wiktionary.org/wiki/Category:German_suffixes 2 | 3 | function generateSuffixHandler (suffix) { 4 | const suffixLength = suffix.length 5 | const suffixTranslation = suffix 6 | const suffixMeaning = suffix 7 | 8 | const exceptions = [ 9 | ] 10 | 11 | function test (word) { 12 | word = word.toLowerCase() 13 | return word.endsWith(suffix) && !exceptions.includes(word) 14 | } 15 | 16 | function unwrap (word, unsafe) { 17 | if (unsafe || test(word)) { 18 | return word.slice(0, word.length - suffixLength) 19 | } 20 | } 21 | 22 | function translate (word, translator) { 23 | if (test (word)) { 24 | return translator(unwrap(word, true)) + " + " + suffixTranslation 25 | } 26 | } 27 | 28 | function annotate (word) { 29 | if (test (word)) { 30 | return unwrap(word, true) + " + " + suffixMeaning 31 | } 32 | } 33 | 34 | return { 35 | test, 36 | unwrap, 37 | translate, 38 | annotate, 39 | exceptions, 40 | affix: suffix, 41 | affixType: 'suffix', 42 | } 43 | } 44 | 45 | export const suffixes = [ 46 | "'", 47 | "'s", 48 | "'sch", 49 | 50 | "abel", 51 | "affin", 52 | "age", 53 | "al", 54 | "algie", 55 | "ämie", 56 | "ant", 57 | "anz", 58 | "är", 59 | "archie", 60 | "artig", 61 | "at", 62 | 63 | "bar", 64 | "beck", 65 | "bold", 66 | "broich", 67 | "bühren", 68 | "büren", 69 | 70 | "chen", 71 | "chens", 72 | "chor", 73 | 74 | "de", 75 | 76 | "e", 77 | "ei", 78 | "ektomie", 79 | "el", 80 | "elchen", 81 | "ell", 82 | "eln", 83 | "em", 84 | "en", 85 | "end", 86 | "ens", 87 | "enz", 88 | "er", 89 | "erchen", 90 | "erei", 91 | "erl", 92 | "erlei", 93 | "ern", 94 | "erweise", 95 | "es", 96 | "esk", 97 | "eur", 98 | "euse", 99 | 100 | "fach", 101 | "fähig", 102 | "falt", 103 | "farben", 104 | "fertig", 105 | "förmig", 106 | "frei", 107 | 108 | "gate", 109 | "gen", 110 | "gleich", 111 | "graf", 112 | "grafie", 113 | "grafisch", 114 | "gramm", 115 | "graph", 116 | "graphie", 117 | "graphisch", 118 | 119 | "haft", 120 | "haftig", 121 | "halber", 122 | "haltig", 123 | "heit", 124 | 125 | "i", 126 | "iatrie", 127 | "ien", 128 | "ier", 129 | "ieren", 130 | "iert", 131 | "if", 132 | "ig", 133 | "igen", 134 | "igkeit", 135 | "ik", 136 | "iker", 137 | "in", 138 | "ine", 139 | "ing", 140 | "ion", 141 | "iren", 142 | "is", 143 | "isch", 144 | "isieren", 145 | "isierung", 146 | "ismus", 147 | "ist", 148 | "istisch", 149 | "it", 150 | "ität", 151 | "iter", 152 | "itis", 153 | "iv", 154 | 155 | "jährig", 156 | 157 | "keit", 158 | "ken", 159 | "kettig", 160 | "köpfig", 161 | "kratie", 162 | "kunde", 163 | 164 | "lastig", 165 | "le", 166 | "lei", 167 | "lein", 168 | "ler", 169 | "lich", 170 | "ling", 171 | "lings", 172 | "loge", 173 | "logie", 174 | "los", 175 | "lyse", 176 | 177 | "mal", 178 | "mässig", 179 | "mäßig", 180 | "metrie", 181 | 182 | "n", 183 | "nis", 184 | "niß", 185 | 186 | "oid", 187 | "ologie", 188 | "om", 189 | "ös", 190 | "ose", 191 | "ow", 192 | 193 | "pathie", 194 | "pathisch", 195 | "pflichtig", 196 | "phil", 197 | "philie", 198 | "phob", 199 | "phobie", 200 | "prozentig", 201 | 202 | "reich", 203 | "rich", 204 | 205 | "s", 206 | "sal", 207 | "sam", 208 | "sch", 209 | "schaft", 210 | "se", 211 | "seits", 212 | "sel", 213 | "sen", 214 | "seyts", 215 | "st", 216 | "stämmig", 217 | "ste", 218 | 219 | "t", 220 | "tägig", 221 | "te", 222 | "thum", 223 | "thumb", 224 | "tion", 225 | "trop", 226 | "tum", 227 | "tume", 228 | "tümer", 229 | "tümern", 230 | "tumes", 231 | "tums", 232 | 233 | "ung", 234 | "urie", 235 | 236 | "verdächtig", 237 | "vitz", 238 | "voll", 239 | 240 | "wärtig", 241 | "wärts", 242 | "weise", 243 | "wert", 244 | "wertig", 245 | "wesen", 246 | "witz", 247 | "wöchig", 248 | "würdig", 249 | 250 | "zentrisch", 251 | "zid", 252 | ] 253 | 254 | export const handlers = suffixes.map(generateSuffixHandler) 255 | export default handlers -------------------------------------------------------------------------------- /vanilla/scripts/reader-alternative/dom-interaction.js: -------------------------------------------------------------------------------- 1 | import { gParseRawText, punctuation } from './text-parsing.js' 2 | import { sequence } from './func-utils.js' 3 | import { isNumber, sanitizeWord, smartTrimLeft, smartTrimRight } from './str-utils.js' 4 | 5 | export function fillTextNode ($textNode, rawText, append=false) { 6 | if (!append) { 7 | $textNode.innerText = '' 8 | } 9 | for (let p of gParseRawText(rawText)) { 10 | const $p = document.createElement('p') 11 | $p.className = 'paragraph' 12 | for (let s of p) { 13 | const $s = document.createElement('span') 14 | $s.className = 'sentence' 15 | for (let w of s) { 16 | const $w = document.createElement('span') 17 | $w.className = 'word' 18 | let $sp, $pt1, $pt2 19 | let $wp = document.createElement('span') 20 | 21 | const wTrimmed1 = smartTrimLeft(w, punctuation) 22 | if (wTrimmed1.length < w.length) { 23 | $pt1 = document.createElement('punctuation') 24 | $pt1.innerText = w.slice(0, w.length - wTrimmed1.length) 25 | $pt1.className = 'punctuation' 26 | } 27 | const wTrimmed2 = wTrimmed1.trimRight(' ') 28 | 29 | if (wTrimmed2.length < wTrimmed1.length) { 30 | $sp = document.createElement('span') 31 | $sp.innerText = wTrimmed1.slice(wTrimmed1.length - (wTrimmed1.length - wTrimmed2.length)) 32 | $sp.className = 'whitespaces' 33 | } 34 | 35 | const wTrimmed3 = smartTrimRight(wTrimmed2, punctuation) 36 | 37 | if (wTrimmed3.length < wTrimmed2.length) { 38 | $pt2 = document.createElement('span') 39 | $pt2.innerText = wTrimmed2.slice(wTrimmed2.length - (wTrimmed2.length - wTrimmed3.length)) 40 | $pt2.className = 'punctuation' 41 | } 42 | 43 | $wp.innerText = wTrimmed3 44 | const numerical = isNumber(wTrimmed3) 45 | const sanitized = sanitizeWord(wTrimmed3) 46 | const lowercased = wTrimmed3.toLocaleLowerCase() 47 | const isWeird = lowercased !== sanitized // && !parseInt(wTrimmed2) 48 | if (isWeird) { 49 | console.log('weird', wTrimmed3, lowercased, sanitized) 50 | } 51 | $wp.className = 52 | numerical 53 | ? 'word-number' 54 | : !isWeird 55 | ? 'word-proper' 56 | : 'word-weird' 57 | 58 | if ($pt1) { 59 | $w.appendChild($pt1) 60 | } 61 | $w.appendChild($wp) 62 | if ($pt2) { 63 | $w.appendChild($pt2) 64 | } 65 | if ($sp) { 66 | $w.appendChild($sp) 67 | } 68 | 69 | $s.appendChild($w) 70 | } 71 | $p.appendChild($s) 72 | } 73 | $textNode.appendChild($p) 74 | } 75 | } 76 | 77 | export function fixViewStyleClasses (selectionState) { 78 | for (let $paragraph of selectionState.$textNode.childNodes) { 79 | $paragraph.classList.toggle('selected', 80 | $paragraph === selectionState.$selectedParagraph 81 | ) 82 | for (let $sentence of $paragraph.childNodes) { 83 | $sentence.classList.toggle('selected', 84 | $sentence === selectionState.$selectedSentence 85 | ) 86 | for (let $word of $sentence.childNodes) { 87 | const $wp = $word.getElementsByClassName('word-proper')[0] 88 | if (!$wp) { 89 | continue 90 | } 91 | $word.classList.toggle('selected', 92 | $word === selectionState.$selectedWord 93 | ) 94 | $word.classList.toggle('occurence', 95 | sanitizeWord($wp.innerText) === selectionState.selectedWordSanitized 96 | ) 97 | } 98 | } 99 | } 100 | } 101 | 102 | export function wordClickHandler (e, selectionState, afterStateChange) { 103 | if (e.target.classList.contains('word-proper')) { 104 | e.preventDefault() 105 | e.stopPropagation() 106 | const selectedWordSanitized = sanitizeWord(e.target.innerText) 107 | if (selectionState.$selectedWord === e.target) { 108 | selectionState.selectedWord = '' 109 | selectionState.selectedWordSanitized = '' 110 | selectionState.$selectedWord = null 111 | selectionState.$selectedSentence = null 112 | selectionState.$selectedParagraph = null 113 | } else { 114 | selectionState.selectedWord = e.target.innerText 115 | selectionState.selectedWordSanitized = selectedWordSanitized 116 | selectionState.$selectedWord = e.target.parentNode 117 | selectionState.$selectedSentence = selectionState.$selectedWord.parentNode 118 | selectionState.$selectedParagraph = selectionState.$selectedSentence.parentNode 119 | } 120 | afterStateChange(selectionState) 121 | } 122 | } 123 | 124 | export function registerWordClickHandler ($textNode, selectionState, afterStateChange) { 125 | $textNode.addEventListener('click', (e) => wordClickHandler(e, selectionState, afterStateChange)) 126 | } 127 | 128 | export function newSelectionState ($textNode) { 129 | return { 130 | $textNode: $textNode, 131 | 132 | $selectedParagraph: null, 133 | $selectedSentence: null, 134 | $selectedWord: null, 135 | 136 | selectedWord: "", 137 | selectedWordSanitized: "", 138 | } 139 | } 140 | 141 | export function initReader ($textNode, onSelectionStateChange) { 142 | const selectionState = newSelectionState($textNode) 143 | registerWordClickHandler( 144 | $textNode, 145 | selectionState, 146 | sequence(fixViewStyleClasses, onSelectionStateChange) 147 | ) 148 | return selectionState 149 | } 150 | 151 | export default initReader 152 | -------------------------------------------------------------------------------- /vanilla/scripts/reader/text.js: -------------------------------------------------------------------------------- 1 | export const text = 2 | ` 3 | Yuval Noah Harari 4 | Sapiens. Eine kurze Geschichte der Menschheit. 5 | Teil 1. Die Kognitive Revolution. 6 | Kapitel 1. Ein ziemlich unauffälliges Tier. 7 | Vor rund 13,5 Milliarden Jahren entstanden Materie, Energie, Raum und Zeit in einem Ereignis namens Urknall. Die Geschichte dieser grundlegenden Eigenschaften unseres Universums nennen wir Physik. 8 | Etwa 300000 Jahre später verbanden sich Materie und Energie zu komplexeren Strukturen namens Atome, die sich wiederum zu Molekülen zusammenschlossen. Die Geschichte der Atome, Moleküle und ihrer Reaktionen nennen wir Chemie. 9 | Vor 3,8 Milliarden Jahren begannen auf einem Planeten namens Erde bestimmte Moleküle, sich zu besonders großen und komplexen Strukturen zu verbinden, die wir als Organismen bezeichnen. Die Geschichte dieser Organismen nennen wir Biologie. 10 | Und vor gut 70000 Jahren begannen Organismen der Art Homo sapiens mit dem Aufbau von noch komplexeren Strukturen namens Kulturen. Die Entwicklung dieser Kulturen nennen wir Geschichte. 11 | Die Geschichte der menschlichen Kulturen wurde von drei großen Revolutionen geprägt. Die kognitive Revolution vor etwa 70000 Jahren brachte die Geschichte überhaupt erst in Gang. Die landwirtschaftliche Revolution vor rund 12000 Jahren beschleunigte sie. Und die wissenschaftliche Revolution, die vor knapp 500 Jahren ihren Anfang nahm, könnte das Ende der Geschichte und der Beginn von etwas völlig Neuem sein. Dieses Buch erzählt, welche Konsequenzen diese drei Revolutionen für den Menschen und seine Mitlebewesen hatten und haben. 12 | Menschen gab es schon lange vor dem Beginn der Geschichte. Die ersten menschenähnlichen Tiere betraten vor etwa 2,5 Millionen Jahren die Bühne. Aber über zahllose Generationen hinweg stachen sie nicht aus der Vielzahl der Tiere heraus, mit denen sie ihren Lebensraum teilten. Wenn wir 2 Millionen Jahre in die Vergangenheit reisen und einen Spaziergang durch Ostafrika unternehmen könnten, würden wir dort vermutlich Gruppen von Menschen begegnen, die äußerlich gewisse Ähnlichkeit mit uns haben. Besorgte Mütter tragen ihre Babys auf dem Arm, Kinder spielen im Matsch. Von irgendwoher dringt das Geräusch von Steinen, die aufeinandergeschlagen werden, und wir sehen einen ernst dreinblickenden jungen Mann, der sich in der Kunst der Werkzeugherstellung übt. Die Technik hat er sich bei zwei Männern abgeschaut, die sich gerade um einen besonders fein gearbeiteten Feuerstein streiten; knurrend und mit gefletschten Zähnen tragen sie eine weitere Runde im Kampf um die Vormachtstellung in der Gruppe aus. Währenddessen zieht sich ein älterer Herr mit weißen Haaren aus dem Trubel zurück und streift allein durch ein nahe gelegenes Waldstück, wo er von einer Horde Schimpansen überrascht wird. 13 | Diese Menschen liebten, stritten, zogen ihren Nachwuchs auf und erfanden Werkzeuge – genau wie die Schimpansen. Niemand, schon gar nicht die Menschen selbst, konnte ahnen, dass ihre Nachfahren eines Tages über den Mond spazieren, Atome spalten, das Genom entschlüsseln oder Geschichtsbücher schreiben würden. Die prähistorischen Menschen waren unauffällige Tiere, die genauso viel oder so wenig Einfluss auf ihre Umwelt hatten wie Gorillas, Libellen oder Quallen. 14 | Biologen teilen Lebewesen in verschiedene Arten ein. Tiere gehören derselben Art an, wenn sie sich miteinander paaren und fortpflanzungsfähige Nachkommen zeugen. Pferde und Esel haben einen gemeinsamen Vorfahren und viele gemeinsame Eigenschaften, doch was die Fortpflanzung angeht, haben sie kein Interesse aneinander. Man kann sie zwar dazu bringen, sich zu paaren, doch die Maultiere, die aus dieser Verbindung hervorgehen, sind unfruchtbar. Das ist ein Zeichen dafür, dass sie unterschiedlichen Arten angehören. Anders Bulldoggen und Cockerspaniel: Sie unterscheiden sich zwar äußerlich ganz erheblich, doch sie paaren sich sehr bereitwillig, und ihr Nachwuchs kann mit anderen Hunden neue Welpen zeugen. Bulldoggen und Cockerspaniel sind also Angehörige derselben Art, nämlich der Hunde. 15 | Arten mit einem gemeinsamen Vorfahren werden oft zu Gattungen zusammengefasst. Löwen, Tiger, Leoparden und Jaguare sind beispielsweise unterschiedliche Arten der Gattung Panthera. Biologen geben Lebewesen zweiteilige lateinische Namen: der erste Teil bezeichnet die Gattung, der zweite die Art. Der Löwe heißt zum Beispiel Panthera leo: die Art Leo aus der Gattung der Panthera. Als Leser dieses Buchs gehören Sie vermutlich den Homo sapiens an – der Art Sapiens (weise) aus der Gattung Homo (Mensch). 16 | Gattungen werden wiederum zu Familien zusammengefasst, zum Beispiel den Katzen (Löwen, Geparden, Hauskatzen), Hunden (Wölfe, Füchse, Schakale) oder Elefanten (Elefanten, Mammuts, Mastodonten). Alle Angehörigen einer Familie lassen sich auf einen gemeinsamen Urahn zurückführen. Alle Katzen, vom zahmsten Hauskätzchen zum wildesten Löwen, gehen auf einen gemeinsamen Katzenvorfahren zurück, der vor rund 25 Millionen Jahren lebte. 17 | Natürlich gehört auch der Homo sapiens einer Familie an. Diese scheinbar so banale Tatsache war eines der bestgehüteten Geheimnisse der Geschichte. Der Homo sapiens tat nämlich lange so, als habe er nichts mit dem Rest der Tierwelt zu tun und sei ein Waisenkind ohne Geschwister und Vettern und vor allem ohne Eltern. Das ist natürlich nicht der Fall. Ob es uns gefällt oder nicht, wir gehören der großen und krawalligen Familie der Menschenaffen an. Unsere nächsten lebenden Verwandten sind Gorillas und Orang-Utans. Am allernächsten stehen uns jedoch die Schimpansen. Vor gerade einmal sechs Millionen Jahren brachte eine Äffin zwei Töchter zur Welt: Eine der beiden wurde die Urahnin aller Schimpansen, die andere ist unsere eigene Ur-Ur-Ur-Großmutter.` 18 | 19 | export default text -------------------------------------------------------------------------------- /vanilla/scripts/reader.js: -------------------------------------------------------------------------------- 1 | import initText from "./reader/initText.js" 2 | import { getTranslation, isTranslated, setTranslation } from './dict.js' 3 | import { requestLocalTranslation } from './local-dictionary.js' 4 | import text from './reader/text.js' 5 | import { test as morphologyTest } from './morphology.js' 6 | 7 | const $originalWord = document.getElementById('originalWord') 8 | const $dictionaryForm = document.getElementById('dictionaryForm') 9 | // const $occurences = document.getElementById('occurences') 10 | const $translationEn = document.getElementById('translationEn') 11 | const $translationRu = document.getElementById('translationRu') 12 | const $text = document.getElementById('text') 13 | const $remoteDictInfo = document.getElementById('remote-dict-info') 14 | 15 | let currentLanguage = 'de' 16 | let selectedWord = '' 17 | let dictionaryForm = '' 18 | let translationEn = '' 19 | let translationRu = '' 20 | // let occurences = 0 21 | 22 | let remoteDictInfo = null 23 | 24 | let queryLocalDictionary = false 25 | const $defaultTextCopyrightNote = document.getElementById('default-text-copyright-note') 26 | 27 | const handleWordClick = (word) => { 28 | if (selectedWord === word) { 29 | selectedWord = '' 30 | } else { 31 | selectedWord = word 32 | resetRemoteDictInfo() 33 | if (queryLocalDictionary) { 34 | requestLocalTranslation(selectedWord) 35 | .then(updateRemoteDictInfo) 36 | .catch(_ => {}) 37 | } 38 | performMorphologyTest() 39 | } 40 | updateWordSelectionIndication() 41 | checkTranslations() 42 | updateInfo() 43 | } 44 | 45 | const resetRemoteDictInfo = () => { 46 | remoteDictInfo = null 47 | $remoteDictInfo.innerText = '' 48 | } 49 | 50 | const updateRemoteDictInfo = (rdi) => { 51 | remoteDictInfo = rdi 52 | $remoteDictInfo.innerText = JSON.stringify(rdi, null, 2) 53 | } 54 | 55 | const performMorphologyTest = () => { 56 | const results = morphologyTest(selectedWord) 57 | for (let a of results.analysis) { 58 | if (Array.isArray(a) && a.length > 0) { 59 | console.log('the dictionary form may be ' + a[1]) 60 | if (queryLocalDictionary) { 61 | requestLocalTranslation(a[1]).then(response => { 62 | if (remoteDictInfo === null) { 63 | updateRemoteDictInfo(response) 64 | } 65 | }) 66 | } 67 | } 68 | } 69 | } 70 | 71 | const updateWordSelectionIndication = () => { 72 | // occurences = 0; 73 | [...document.getElementsByClassName('word')].map($w => { 74 | const selected = $w.innerText.toLowerCase() === selectedWord.toLowerCase() 75 | // if (selected) { 76 | // occurences++ 77 | // } 78 | $w.classList.toggle('selected', selected) 79 | }) 80 | } 81 | 82 | const renderDictPresenceStatus = () => { 83 | [...document.getElementsByClassName('word')].map($w => { 84 | const w = $w.innerText.toLowerCase() 85 | $w.classList.toggle('translated', isTranslated(currentLanguage, w)) 86 | }) 87 | } 88 | 89 | const updateInfo = () => { 90 | $originalWord.innerText = 91 | selectedWord === dictionaryForm || !dictionaryForm 92 | ? selectedWord 93 | : `${selectedWord} (${dictionaryForm || '?'})` 94 | // $dictionaryForm.innerText = dictionaryForm 95 | // $occurences.innerText = occurences 96 | $translationEn.innerText = translationEn || '' 97 | $translationRu.innerText = translationRu || '' 98 | } 99 | 100 | const checkTranslations = () => { 101 | translationEn = getTranslation(currentLanguage, 'en', selectedWord) 102 | translationRu = getTranslation(currentLanguage, 'ru', selectedWord) 103 | dictionaryForm = getTranslation(currentLanguage, 'dictionaryForm', selectedWord) 104 | } 105 | 106 | const putOwnText = (text) => { 107 | initText($text, text) 108 | setTimeout(renderDictPresenceStatus, 0) 109 | $defaultTextCopyrightNote && ($defaultTextCopyrightNote.style.display = 'none') 110 | } 111 | 112 | export function initReaderPage (useDemoText) { 113 | 114 | document.body.addEventListener('click', (e) => { 115 | if (e.target.classList.contains('word')) { 116 | handleWordClick(e.target.innerText) 117 | } 118 | }) 119 | 120 | document.body.addEventListener('keyup', (e) => { 121 | if (!selectedWord) { 122 | return 123 | } 124 | if (e.target.classList.contains('translation')) { 125 | const translationText = e.target.innerText 126 | switch (e.target.id) { 127 | case 'translationRu': 128 | setTranslation(currentLanguage, 'ru', selectedWord, translationText) 129 | break 130 | case 'translationEn': 131 | setTranslation(currentLanguage, 'en', selectedWord, translationText) 132 | break 133 | } 134 | setTimeout(renderDictPresenceStatus, 0) 135 | } 136 | }, true) 137 | 138 | document.getElementById('remote-dict-info-visibility-toggler').addEventListener('click', (event) => { 139 | queryLocalDictionary = !queryLocalDictionary 140 | event.currentTarget.parentNode.classList.toggle('visible', queryLocalDictionary) 141 | if (queryLocalDictionary) { 142 | requestLocalTranslation(selectedWord) 143 | .then(updateRemoteDictInfo) 144 | .catch(_ => {}) 145 | } 146 | }) 147 | 148 | if (useDemoText) { 149 | initText($text, text) 150 | renderDictPresenceStatus() 151 | } 152 | 153 | const $ownTextButton = document.getElementById('put-own-text-button') 154 | const $ownTextInput = document.getElementById('put-own-text-input') 155 | $ownTextButton?.addEventListener('click', () => { 156 | if ($ownTextInput) { 157 | putOwnText($ownTextInput.value) 158 | $ownTextInput.value = '' 159 | } else { 160 | putOwnText(prompt('copy-paste your text here; yep, it may be rather large')) 161 | } 162 | }) 163 | 164 | window.putOwnText = putOwnText 165 | 166 | const $currentTextlanguageSelector = document.getElementById('current-text-language-selector') 167 | if ($currentTextlanguageSelector) { 168 | currentLanguage = $currentTextlanguageSelector.value 169 | $currentTextlanguageSelector.addEventListener('input', () => { 170 | currentLanguage = $currentTextlanguageSelector.value 171 | renderDictPresenceStatus() 172 | checkTranslations() 173 | updateInfo() 174 | }) 175 | } 176 | } 177 | 178 | 179 | export default initReaderPage 180 | -------------------------------------------------------------------------------- /specific-language-tools/german/text-specific-dictionaries/harari.js: -------------------------------------------------------------------------------- 1 | export const dict = { 2 | "dictionaryForm": { 3 | "abgeschaut": "abschauen", 4 | "ahnen": "ahnen", 5 | "albern": "albern", 6 | "allernächsten": "allernächsten", 7 | "angepasst": "anpassen", 8 | "anspruch": "anspruch", 9 | "art": "die Art", 10 | "aufbau": "der Aufbau", 11 | "aufeinanderfolgten": "aufeinanderfolgen", 12 | "aufrechte": "aufrecht", 13 | "aufwand?": "der Aufwand", 14 | "ausgrabungen": "die Ausgrabung", 15 | "auswanderergruppen": "die Gruppe", 16 | "bescheidenheit": "die Bescheidenheit", 17 | "beschleunigte": "beschleunigen", 18 | "bestgehüteten": "hüten", 19 | "bestimmte": "bestimmt", 20 | "bewahrt": "bewahrt", 21 | "bezeichnen": "bezeichnen", 22 | "derart": "derart", 23 | "dreinblickenden": "dreinblicken", 24 | "ebenfalls": "ebenfalls", 25 | "eigenschaften": "die Eigenschaft", 26 | "eingestellt": "einstellen", 27 | "entstanden": "entstehen", 28 | "entwickelten": "entwickeln", 29 | "entwicklung": "die Entwicklung", 30 | "ereignis": "das Ereignis", 31 | "erfanden": "erfinden", 32 | "ergaben": "ergeben", 33 | "ergebnis": "das Ergebnis", 34 | "erheblich": "erheblich", 35 | "erstaunlich": "erstaunlich", 36 | "erweckt": "erwecken", 37 | "feige": "feig", 38 | "fortpflanzung": "die Fortpflanzung", 39 | "fortpflanzungsfähige": "fortpflanzungsfähig", 40 | "fähigkeiten": "die Fähigkeit", 41 | "gang": "der Gang", 42 | "gattung": "die Gattung", 43 | "gattungen": "die Gattung", 44 | "gefletschten": "fletschen", 45 | "gehütet": "hüten", 46 | "geprägt": "prägen", 47 | "geräusch": "das Geräusch", 48 | "geschichte": "die Geschichte", 49 | "getauft": "taufen", 50 | "gewisse": "gewiss", 51 | "grundlegenden": "grundlegend", 52 | "herumgeschleppt": "herumgeschleppen", 53 | "hervorbrachte": "hervorbringen", 54 | "hervorging": "hervorgehen", 55 | "hirschgeweihen": "das Hirschgeweih", 56 | "hochtrabend": "hochtrabend", 57 | "hüften": "die Hüfte", 58 | "jedoch": "jedoch", 59 | "katzenvorfahren": "der Vorfahre", 60 | "keller": "der Keller", 61 | "knurrend": "knurren", 62 | "kraft": "die Kraft", 63 | "krawalligen": "krawallen", 64 | "last": "die Last", 65 | "lebewesen": "das Lebenswesen", 66 | "leichen": "die Leiche", 67 | "leistungsfähigen": "leistungsfähig", 68 | "libellen": "die Libelle", 69 | "matsch": "der Matsch", 70 | "maultiere": "der Maultier", 71 | "menschenaffen": "der Menschenaffe", 72 | "menschenähnlichen": "menschenähnlich", 73 | "menschheit": "die Menschheit", 74 | "mitlebewesen": "das Mitlebewesen", 75 | "muckis": "der Muskel", 76 | "munter": "munter", 77 | "nachwuchs": "der Nachwuchs", 78 | "namens": "namens", 79 | "pfauenfedern": "die Pfauenfeder", 80 | "quallen": "die Qualle", 81 | "reihe": "die Reihe", 82 | "richtungen": "die Richtung", 83 | "rückblickend": "rückblickend", 84 | "salopp": "salopp", 85 | "scheinbar": "scheinbar", 86 | "schmalere": "schmal", 87 | "schwülen": "schwül", 88 | "schädel": "der Schädel", 89 | "spalten": "spalten", 90 | "speerschwingenden": "schwingen", 91 | "stachen": "stechen", 92 | "stammbaum": "der Stammbaum", 93 | "stießen": "stoßen", 94 | "streift": "streifen", 95 | "stritten": "streiten", 96 | "säugetiere": "das Säugetier", 97 | "säuglinge": "der Säugling", 98 | "tatsache": "die Tatsache", 99 | "trubel": "der Trubel", 100 | "umstellung": "die Umstellung", 101 | "unauffällige": "unauffällig", 102 | "unauffälliges": "unauffällig", 103 | "unfruchtbar": "unfruchtbar", 104 | "unhandlichen": "unhandlich", 105 | "unmengen": "die Unmenge", 106 | "urahn": "der Urahn", 107 | "urahnin": "die Urahnin", 108 | "urknall": "der Urknall", 109 | "verbanden": "verbinden", 110 | "verdrängen": "verdrängen", 111 | "verfügen": "verfügen", 112 | "verlust": "der Verlust", 113 | "vermutlich": "vermutlich", 114 | "verschneiten": "schneien", 115 | "versteinerten": "versteinert", 116 | "verwandten": "verwanden", 117 | "vettern": "der Vetter", 118 | "vielfalt": "die Vielfalt", 119 | "vielzahl": "die Vielzahl", 120 | "vorfahren": "der Vorfahre", 121 | "vorläufermodelle": "das Vorläufermodell", 122 | "vormachtstellung": "die Vormachtstellung", 123 | "waisenkind": "das Waisenkind", 124 | "werkzeugherstellung": "die Herstellung", 125 | "wett?": "wett", 126 | "wiederum": "wiederum", 127 | "wiege": "die Wiege", 128 | "wog": "wiegen", 129 | "währenddessen": "währenddessen", 130 | "zahmsten": "zahm", 131 | "zeugen": "zeugen", 132 | "zogen": "ziehen", 133 | "zusammenschlossen": "schließen", 134 | "zuwachs": "der Zuwachs", 135 | "zweiteilige": "zweiteilig", 136 | "äußerlich": "äußerlich" 137 | }, 138 | "en": { 139 | "abgeschaut": "peep, copied", 140 | "ahnen": "suspect", 141 | "albern": "inane", 142 | "allernächsten": "very next", 143 | "angepasst": "adapted", 144 | "anspruch": "claim", 145 | "art": "kind", 146 | "aufbau": "building", 147 | "aufeinanderfolgten": "followed one another", 148 | "aufrechte": "upright", 149 | "aufwand?": "expenditure", 150 | "ausgrabungen": "excavations", 151 | "auswanderergruppen": "emigrant groups", 152 | "bescheidenheit": "humility, modesty", 153 | "beschleunigte": "sped up", 154 | "bestgehüteten": "best-kept", 155 | "bestimmte": "certain", 156 | "bewahrt": "preserved", 157 | "bezeichnen": "describe", 158 | "derart": "to such an extent", 159 | "dreinblickenden": "looking", 160 | "ebenfalls": "likewise", 161 | "eigenschaften": "properties", 162 | "eingestellt": "adjusted", 163 | "entstanden": "emerged", 164 | "entwickelten": "developed", 165 | "entwicklung": "development", 166 | "ereignis": "event", 167 | "erfanden": "invent", 168 | "ergaben": "resulted", 169 | "ergebnis": "result", 170 | "erheblich": "considerable", 171 | "erstaunlich": "amazing", 172 | "erweckt": "awakened", 173 | "feige": "cowardly", 174 | "fortpflanzung": "reproduction", 175 | "fortpflanzungsfähige": "reproductive-able", 176 | "fähigkeiten": "skills", 177 | "gang": "gait", 178 | "gattung": "genus", 179 | "gattungen": "genera", 180 | "gefletschten": "bared", 181 | "gehütet": "protected", 182 | "geprägt": "embossed, shaped, formed", 183 | "geräusch": "noise", 184 | "geschichte": "history", 185 | "getauft": "baptized", 186 | "gewisse": "certain", 187 | "grundlegenden": "fundamental", 188 | "herumgeschleppt": "dragged around", 189 | "hervorbrachte": "brought forth", 190 | "hervorging": "emerged", 191 | "hirschgeweihen": "deer antlers", 192 | "hochtrabend": "grandiose, pompous", 193 | "hüften": "hips", 194 | "jedoch": "however", 195 | "katzenvorfahren": "cat ansectors", 196 | "keller": "cellar", 197 | "knurrend": "growling", 198 | "kraft": "force", 199 | "krawalligen": "rowdy", 200 | "last": "burden", 201 | "lebewesen": "lifeform", 202 | "leichen": "corpses", 203 | "leistungsfähigen": "powerful", 204 | "libellen": "dragonflies", 205 | "matsch": "mud; passion", 206 | "maultiere": "mules", 207 | "menschenaffen": "great apes", 208 | "menschenähnlichen": "humanlike", 209 | "menschheit": "humankind", 210 | "mitlebewesen": "fellow creatures", 211 | "muckis": "muscles", 212 | "munter": "lively", 213 | "nachwuchs": "offsprings", 214 | "namens": "called", 215 | "pfauenfedern": "peacock feathers", 216 | "quallen": "jellyfish", 217 | "reihe": "row", 218 | "richtungen": "directions", 219 | "rückblickend": "retrospectively", 220 | "salopp": "casually, slangy", 221 | "scheinbar": "seemingly", 222 | "schmalere": "narrower", 223 | "schwülen": "sultry", 224 | "schädel": "skull", 225 | "spalten": "split", 226 | "speerschwingenden": "spear-wielding", 227 | "stachen": "stung", 228 | "stammbaum": "pedigree, genealogical tree", 229 | "stießen": "bumped", 230 | "streift": "walks", 231 | "stritten": "argued", 232 | "säugetiere": "mammals", 233 | "säuglinge": "baby", 234 | "tatsache": "fact", 235 | "trubel": "hustle", 236 | "umstellung": "changeover", 237 | "unauffällige": "unremarkable", 238 | "unauffälliges": "unremarkable", 239 | "unfruchtbar": "barren", 240 | "unhandlichen": "unwieldly", 241 | "unmengen": "lashings", 242 | "urahn": "ancestor", 243 | "urahnin": "ancestral mother", 244 | "urknall": "Big Bang", 245 | "verbanden": "bond", 246 | "verdrängen": "to push away", 247 | "verfügen": "to feature", 248 | "verlust": "loss", 249 | "vermutlich": "allegedly, presumably", 250 | "verschneiten": "snowy", 251 | "versteinerten": "petrified", 252 | "verwandten": "relatives", 253 | "vettern": "cousins", 254 | "vielfalt": "diversity", 255 | "vielzahl": "variety", 256 | "vorfahren": "ancestors", 257 | "vorläufermodelle": "previous models", 258 | "vormachtstellung": "supremacy", 259 | "waisenkind": "orphan", 260 | "werkzeugherstellung": "instrument creation", 261 | "wett?": "worth", 262 | "wiederum": "in turn", 263 | "wiege": "wiege", 264 | "wog": "weighed", 265 | "währenddessen": "meanwhile", 266 | "zahmsten": "tamest", 267 | "zeugen": "testify; beget", 268 | "zogen": "pulled", 269 | "zusammenschlossen": "united", 270 | "zuwachs": "growth", 271 | "zweiteilige": "two-part", 272 | "äußerlich": "externally" 273 | }, 274 | "ru": { 275 | "abgeschaut": "подглядел, скопировал", 276 | "ahnen": "подозревать", 277 | "albern": "глупо", 278 | "allernächsten": "самые ближние; совсем рядом", 279 | "angepasst": "адаптировавшийся", 280 | "anspruch": "притязание, претензия", 281 | "art": "вид", 282 | "aufbau": "строительство", 283 | "aufeinanderfolgten": "следовали друг за другом", 284 | "aufrechte": "прямостоящий", 285 | "aufwand?": "расходы", 286 | "ausgrabungen": "раскопки", 287 | "auswanderergruppen": "группы переселенцев", 288 | "bescheidenheit": "смирение, скромность", 289 | "beschleunigte": "ускорил", 290 | "bestgehüteten": "наиболее оберегаемый", 291 | "bestimmte": "определённая", 292 | "bewahrt": "сохранён", 293 | "bezeichnen": "описывать", 294 | "derart": "настолько сильно", 295 | "dreinblickenden": "смотрящий", 296 | "ebenfalls": "также, равным образом", 297 | "eigenschaften": "характеристики", 298 | "eingestellt": "приспособленный", 299 | "entstanden": "появились", 300 | "entwickelten": "развили", 301 | "entwicklung": "развитие", 302 | "ereignis": "событие", 303 | "erfanden": "изобретали", 304 | "ergaben": "дали результат", 305 | "ergebnis": "результат", 306 | "erheblich": "значительно", 307 | "erstaunlich": "удивительный", 308 | "erweckt": "пробудило", 309 | "feige": "трусливые", 310 | "fortpflanzung": "размножение", 311 | "fortpflanzungsfähige": "плодородные", 312 | "fähigkeiten": "навыки", 313 | "gang": "походка", 314 | "gattung": "род", 315 | "gattungen": "рода", 316 | "gefletschten": "обнажённый", 317 | "gehütet": "сберёг", 318 | "geprägt": "сформированный", 319 | "geräusch": "шум", 320 | "geschichte": "история", 321 | "getauft": "окрещён", 322 | "gewisse": "определённая", 323 | "grundlegenden": "основополагающий", 324 | "herumgeschleppt": "влачимый вокруг", 325 | "hervorbrachte": "взрастил", 326 | "hervorging": "появился", 327 | "hirschgeweihen": "оленьи рога", 328 | "hochtrabend": "грандиозный, помпезный", 329 | "hüften": "бёдра", 330 | "jedoch": "тем не менее", 331 | "katzenvorfahren": "предки кошек", 332 | "keller": "погреб", 333 | "knurrend": "рычащий", 334 | "kraft": "сила", 335 | "krawalligen": "хулиганистые", 336 | "last": "бремя", 337 | "lebewesen": "форма жизни", 338 | "leichen": "трупы", 339 | "leistungsfähigen": "мощный, производительный", 340 | "libellen": "стрекозы", 341 | "matsch": "грязь; азарт", 342 | "maultiere": "мулы", 343 | "menschenaffen": "человекоподобные обезьяны", 344 | "menschenähnlichen": "человекоподобные", 345 | "menschheit": "человечество", 346 | "mitlebewesen": "братья наши меньшие", 347 | "muckis": "мышцы", 348 | "munter": "бодрый", 349 | "nachwuchs": "потомки", 350 | "namens": "под названием", 351 | "pfauenfedern": "павлиньи перья", 352 | "quallen": "медузы", 353 | "reihe": "ряд", 354 | "richtungen": "направления", 355 | "rückblickend": "ретроспективно", 356 | "salopp": "небрежно, грубовато", 357 | "scheinbar": "кажущийся, по-видимому", 358 | "schmalere": "более узкие", 359 | "schwülen": "знойный", 360 | "schädel": "череп", 361 | "spalten": "расщеплять", 362 | "speerschwingenden": "вооружённые копьями", 363 | "stachen": "ужалили", 364 | "stammbaum": "родословная, генеалогическое древо", 365 | "stießen": "наткнулись", 366 | "streift": "прогуливается", 367 | "stritten": "спорили", 368 | "säugetiere": "млекопитающие", 369 | "säuglinge": "младенец", 370 | "tatsache": "факт", 371 | "trubel": "суета", 372 | "umstellung": "переключение", 373 | "unauffällige": "непримечательный", 374 | "unauffälliges": "неприметный", 375 | "unfruchtbar": "бесплодный", 376 | "unhandlichen": "громоздкий", 377 | "unmengen": "прорвы", 378 | "urahn": "предок", 379 | "urahnin": "прародительница", 380 | "urknall": "Большой Взрыв", 381 | "verbanden": "связали", 382 | "verdrängen": "вытеснять", 383 | "verfügen": "располагать", 384 | "verlust": " потеря", 385 | "vermutlich": "якобы, предположительно", 386 | "verschneiten": "заснеженный", 387 | "versteinerten": "окаменелые", 388 | "verwandten": "родственники", 389 | "vettern": "кузены", 390 | "vielfalt": "разнообразие", 391 | "vielzahl": "разнообразие", 392 | "vorfahren": "предки", 393 | "vorläufermodelle": "предшествующие модели", 394 | "vormachtstellung": "превосходство", 395 | "waisenkind": "сирота", 396 | "werkzeugherstellung": "создание инструментов", 397 | "wett?": "стоящий того", 398 | "wiederum": "в свою очередь", 399 | "wiege": "колыбель", 400 | "wog": "весил", 401 | "währenddessen": "тем временем", 402 | "zahmsten": "наиболее приручённый", 403 | "zeugen": "свидетельствовать; зачинать", 404 | "zogen": "тянули", 405 | "zusammenschlossen": "соединились", 406 | "zuwachs": "рост", 407 | "zweiteilige": "двухчастный", 408 | "äußerlich": "внешне" 409 | } 410 | } 411 | 412 | export default dict 413 | --------------------------------------------------------------------------------