├── 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 | Original De
18 | Dictionary Form
19 | Translations
20 |
21 |
22 | En
23 | Ru
24 |
25 |
26 |
27 |
28 |
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 |
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 + `${name} `,
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 |
27 |
28 |
29 | The following text is first two pages of the German translation of "Sapiens" by Yuval Noah Harari.
30 | It's used here as an illustration only. All the respective rights belong to the book's author.
31 |
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 |
27 |
28 |
30 | load text and start reading
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 | 
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 |
--------------------------------------------------------------------------------