mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-24 05:05:20 -04:00
Merge branch '385-multiple-example-sentences-per-morpheme-category' into 'main'
multiple example sentences per morpheme category Closes #385 See merge request PronounsPage/PronounsPage!448
This commit is contained in:
commit
87e4001a20
@ -21,7 +21,8 @@ body[data-theme="dark"] {
|
||||
|
||||
/* BUTTONS */
|
||||
|
||||
.btn-link { color: #fff; }
|
||||
.btn-link { color: $primary-dark; }
|
||||
.btn-link:hover { color: lighten($primary-dark, 10%); }
|
||||
.btn-close { filter: invert(1) grayscale(100%) brightness(200%); }
|
||||
.btn-dark { color: #000; background-color: #f8f9fa; border-color: #f8f9fa; }
|
||||
.btn-dark:hover { color: #000; background-color: #f9fafb; border-color: #f9fafb; }
|
||||
|
@ -14,14 +14,17 @@
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { Example, Pronoun } from '../src/classes.ts';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
example: { required: true },
|
||||
pronoun: { required: true },
|
||||
counter: { default: 0 },
|
||||
example: { required: true, type: Example },
|
||||
pronoun: { required: true, type: Pronoun },
|
||||
counter: { default: 0, type: Number },
|
||||
link: { type: Boolean },
|
||||
pronunciation: { type: Boolean },
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
74
components/ExampleCategory.vue
Normal file
74
components/ExampleCategory.vue
Normal file
@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<li v-if="pronoun">
|
||||
<Example :example="example" :pronoun="pronoun" :counter="counter" :link="link" :pronunciation="pronunciation" />
|
||||
<Tooltip :text="tooltipText">
|
||||
<button
|
||||
v-if="hasDifferentExample"
|
||||
type="button"
|
||||
class="btn btn-sm btn-link p-1"
|
||||
@click="selectDifferentExample()"
|
||||
>
|
||||
<Icon v="random" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import type { PropType } from 'vue';
|
||||
import { ExampleCategory } from '../src/classes.ts';
|
||||
import type { Example, Pronoun } from '../src/classes.ts';
|
||||
import { randomItem } from '../src/helpers.ts';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
exampleCategory: { required: true, type: ExampleCategory },
|
||||
pronounsChoice: { required: true, type: Array as PropType<Pronoun[]> },
|
||||
counter: { default: 0, type: Number },
|
||||
link: { type: Boolean },
|
||||
pronunciation: { type: Boolean },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pronoun: null as Pronoun | null,
|
||||
example: this.exampleCategory.examples[0],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
suitableExamples(): Example[] {
|
||||
return this.exampleCategory.examples.filter((example) => {
|
||||
return this.pronounsChoice.some((pronoun) => example.requiredMorphemesPresent(pronoun, this.counter));
|
||||
});
|
||||
},
|
||||
hasDifferentExample(): boolean {
|
||||
return this.pronounsChoice.length > 1 || this.suitableExamples.length > 1;
|
||||
},
|
||||
tooltipText(): string {
|
||||
if (this.exampleCategory.name) {
|
||||
return this.$t('pronouns.examples.shuffleNamed', { category: this.exampleCategory.name });
|
||||
}
|
||||
return this.$t('pronouns.examples.shuffle');
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.selectPronounForExample(false);
|
||||
},
|
||||
methods: {
|
||||
selectDifferentExample(): void {
|
||||
const changeExample = this.suitableExamples.length > 1;
|
||||
if (changeExample) {
|
||||
const differentExamples = this.suitableExamples.filter((example) => example !== this.example);
|
||||
this.example = randomItem(differentExamples);
|
||||
}
|
||||
this.selectPronounForExample(!changeExample);
|
||||
},
|
||||
selectPronounForExample(forceDifferent: boolean): void {
|
||||
const suitablePronouns = this.pronounsChoice.filter((pronoun) => {
|
||||
return this.example.requiredMorphemesPresent(pronoun) && (!forceDifferent || pronoun !== this.pronoun);
|
||||
});
|
||||
this.pronoun = randomItem(suitablePronouns);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
@ -1,42 +1,46 @@
|
||||
<template>
|
||||
<span
|
||||
:class="['morpheme', 'rounded', highlightedMorpheme == baseMorpheme ? 'bg-primary text-white' : '']"
|
||||
@mouseenter="highlightMorpheme(baseMorpheme)"
|
||||
@mouseleave="highlightMorpheme(null)"
|
||||
@touchstart="highlightMorpheme(highlightedMorpheme == baseMorpheme ? null : baseMorpheme)"
|
||||
:class="['morpheme', 'rounded', isHighlighted ? 'text-bg-primary' : '']"
|
||||
@mouseenter="highlightMorphemes(highlightsMorphemes)"
|
||||
@mouseleave="highlightMorphemes(new Set())"
|
||||
><Spelling escape :text="prepend + pronoun.getMorpheme(morpheme, counter) + append" /></span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import type { PropType } from 'vue';
|
||||
import { Pronoun } from '../src/classes.ts';
|
||||
import { intersection } from '../src/sets.ts';
|
||||
|
||||
export default {
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
pronoun: { required: true },
|
||||
morpheme: { required: true },
|
||||
counter: { default: 0 },
|
||||
pronoun: { required: true, type: Pronoun },
|
||||
morpheme: { required: true, type: String },
|
||||
counter: { default: 0, type: Number },
|
||||
|
||||
prepend: { default: '' },
|
||||
append: { default: '' },
|
||||
prepend: { default: '', type: String },
|
||||
append: { default: '', type: String },
|
||||
highlightsMorphemes: {
|
||||
default() {
|
||||
if (this.morpheme.startsWith('\'')) {
|
||||
return new Set([this.morpheme.substring(1)]);
|
||||
}
|
||||
return new Set([this.morpheme]);
|
||||
},
|
||||
type: Set as PropType<Set<string>>,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
baseMorpheme: {
|
||||
get() {
|
||||
if (this.morpheme[0] == '\'') {
|
||||
return this.morpheme.substring(1);
|
||||
} else {
|
||||
return this.morpheme;
|
||||
}
|
||||
},
|
||||
isHighlighted(): boolean {
|
||||
return intersection(this.highlightsMorphemes, this.$store.state.highlightedMorphemes).size > 0;
|
||||
},
|
||||
...mapState(['highlightedMorpheme']),
|
||||
},
|
||||
methods: {
|
||||
highlightMorpheme(morpheme) {
|
||||
this.$store.commit('highlightMorpheme', morpheme);
|
||||
highlightMorphemes(morphemes: Set<string>): void {
|
||||
this.$store.commit('highlightMorphemes', morphemes);
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
@ -1,25 +1,69 @@
|
||||
<template>
|
||||
<span v-if="pronoun.getMorpheme(morpheme, counter)">
|
||||
<Morpheme :pronoun="pronoun" :morpheme="morpheme" :counter="counter" :prepend="prepend" :append="append" />
|
||||
<Pronunciation
|
||||
v-if="pronoun.pronounceable && pronoun.getPronunciation(morpheme, counter) && !pronoun.getPronunciation(morpheme, counter).startsWith('=')"
|
||||
:pronunciation="`/${prependPr}${pronoun.getPronunciation(morpheme, counter)}${appendPr}/`"
|
||||
text
|
||||
<Popover v-if="pronoun.getMorpheme(morpheme, counter)" resize>
|
||||
<Morpheme
|
||||
:pronoun="pronoun"
|
||||
:morpheme="morpheme"
|
||||
:counter="counter"
|
||||
:prepend="prepend"
|
||||
:append="append"
|
||||
:highlights-morphemes="highlightsMorphemes"
|
||||
/>
|
||||
</span>
|
||||
<Pronunciation v-if="pronunciation" :pronunciation="pronunciation" text />
|
||||
<template v-if="examples.length > 0" #content>
|
||||
<ul class="my-0 ps-3">
|
||||
<li v-for="example in examples" class="my-1">
|
||||
<Example
|
||||
:example="example"
|
||||
:pronoun="pronoun"
|
||||
:counter="counter"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</Popover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
pronoun: { required: true },
|
||||
morpheme: { required: true },
|
||||
counter: { default: 0 },
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import type { PropType } from 'vue';
|
||||
import { Pronoun } from '../src/classes.ts';
|
||||
import { examples } from '../src/data.js';
|
||||
import type { Example } from '../src/classes.ts';
|
||||
|
||||
prepend: { default: '' },
|
||||
prependPr: { default: '' },
|
||||
append: { default: '' },
|
||||
appendPr: { default: '' },
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
pronoun: { required: true, type: Pronoun },
|
||||
morpheme: { required: true, type: String },
|
||||
counter: { default: 0, type: Number },
|
||||
|
||||
prepend: { default: '', type: String },
|
||||
prependPr: { default: '', type: String },
|
||||
append: { default: '', type: String },
|
||||
appendPr: { default: '', type: String },
|
||||
highlightsMorphemes: {
|
||||
default() {
|
||||
if (this.morpheme.startsWith('\'')) {
|
||||
return new Set([this.morpheme.substring(1)]);
|
||||
}
|
||||
return new Set([this.morpheme]);
|
||||
},
|
||||
type: Set as PropType<Set<string>>,
|
||||
},
|
||||
},
|
||||
};
|
||||
computed: {
|
||||
examples(): Example[] {
|
||||
return examples.filter((example) => {
|
||||
return example.requiredMorphemesPresent(this.pronoun, this.counter) &&
|
||||
[...this.highlightsMorphemes.values()].some((morpheme) => example.hasMorpheme(morpheme));
|
||||
});
|
||||
},
|
||||
pronunciation(): string | null {
|
||||
const pronunciation = this.pronoun.getPronunciation(this.morpheme, this.counter);
|
||||
if (!this.pronoun.pronounceable || !pronunciation || pronunciation.startsWith('=')) {
|
||||
return null;
|
||||
}
|
||||
return `/${this.prependPr}${this.pronoun.getPronunciation(this.morpheme, this.counter)}${this.appendPr}/`;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
178
components/Popover.vue
Normal file
178
components/Popover.vue
Normal file
@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<span
|
||||
ref="reference"
|
||||
@mouseenter="show"
|
||||
@mouseleave="hide"
|
||||
><slot></slot><span
|
||||
v-if="visible && hasContent"
|
||||
ref="floating"
|
||||
class="popover"
|
||||
:style="floatingStyles"
|
||||
><span
|
||||
ref="arrow"
|
||||
class="popover-arrow bg-dark text-white"
|
||||
:style="arrowStyles"
|
||||
></span><div
|
||||
ref="content"
|
||||
class="overflow-auto bg-dark text-white px-2 py-1 rounded"
|
||||
><slot name="content"></slot></div></span></span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
import { arrow, autoUpdate, computePosition, flip, offset, shift, size } from '@floating-ui/dom';
|
||||
import Vue from 'vue';
|
||||
import type { CSSProperties } from 'vue/types/jsx';
|
||||
import type { ComputePositionReturn, Placement } from '@floating-ui/dom';
|
||||
|
||||
const getDPR = (element: Element): number => {
|
||||
if (typeof window === 'undefined') {
|
||||
return 1;
|
||||
}
|
||||
const win = element.ownerDocument.defaultView || window;
|
||||
return win.devicePixelRatio || 1;
|
||||
};
|
||||
|
||||
const roundByDPR = (element: Element, value: number): number => {
|
||||
const dpr = getDPR(element);
|
||||
return Math.round(value * dpr) / dpr;
|
||||
};
|
||||
|
||||
const remToPx = (valueInRem: number): number => {
|
||||
return valueInRem * parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||
};
|
||||
|
||||
interface Data {
|
||||
visible: boolean;
|
||||
position: ComputePositionReturn | null;
|
||||
cleanup: (() => void) | null;
|
||||
}
|
||||
|
||||
interface Refs {
|
||||
reference: HTMLSpanElement | undefined;
|
||||
floating: HTMLSpanElement | undefined;
|
||||
content: HTMLSpanElement | undefined;
|
||||
arrow: HTMLSpanElement | undefined;
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
placement: { default: 'bottom', type: String as PropType<Placement> },
|
||||
resize: { type: Boolean },
|
||||
},
|
||||
data(): Data {
|
||||
return {
|
||||
visible: false,
|
||||
position: null,
|
||||
cleanup: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
$tRefs(): Refs {
|
||||
return this.$refs as unknown as Refs;
|
||||
},
|
||||
hasContent(): boolean {
|
||||
return !!this.$slots.content?.[0];
|
||||
},
|
||||
floatingStyles(): CSSProperties | undefined {
|
||||
if (!this.position || !this.$tRefs.floating) {
|
||||
return;
|
||||
}
|
||||
const xVal = roundByDPR(this.$tRefs.floating, this.position.x);
|
||||
const yVal = roundByDPR(this.$tRefs.floating, this.position.y);
|
||||
return {
|
||||
transform: `translate(${xVal}px, ${yVal}px)`,
|
||||
...getDPR(this.$tRefs.floating) >= 1.5 && {
|
||||
willChange: 'transform',
|
||||
},
|
||||
};
|
||||
},
|
||||
arrowStyles(): CSSProperties | undefined {
|
||||
if (!this.position) {
|
||||
return;
|
||||
}
|
||||
|
||||
const arrowData = this.position.middlewareData.arrow;
|
||||
|
||||
const staticSite = {
|
||||
top: 'bottom',
|
||||
right: 'left',
|
||||
bottom: 'top',
|
||||
left: 'right',
|
||||
}[this.position.placement.split('-')[0]]!;
|
||||
|
||||
return {
|
||||
left: typeof arrowData?.x === 'number' ? `${arrowData.x}px` : '',
|
||||
top: typeof arrowData?.y === 'number' ? `${arrowData.y}px` : '',
|
||||
[staticSite]: '-0.25rem',
|
||||
};
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.hide();
|
||||
},
|
||||
methods: {
|
||||
async show() {
|
||||
this.visible = true;
|
||||
// floating element will be rendered on next tick
|
||||
await this.$nextTick();
|
||||
if (!this.$tRefs.reference || !this.$tRefs.floating) {
|
||||
return;
|
||||
}
|
||||
// remove title from reference element to prevent a double tooltip
|
||||
this.$tRefs.reference.removeAttribute('title');
|
||||
this.cleanup = autoUpdate(this.$tRefs.reference, this.$tRefs.floating, () => this.update());
|
||||
},
|
||||
hide() {
|
||||
this.visible = false;
|
||||
if (this.cleanup) {
|
||||
this.cleanup();
|
||||
this.cleanup = null;
|
||||
}
|
||||
},
|
||||
async update() {
|
||||
if (!this.$tRefs.reference || !this.$tRefs.floating || !this.$tRefs.arrow) {
|
||||
return;
|
||||
}
|
||||
const middleware = [offset(remToPx(0.25)), flip(), shift()];
|
||||
if (this.resize) {
|
||||
middleware.push(size({
|
||||
apply: ({ availableWidth, availableHeight }) => {
|
||||
if (!this.$tRefs.content) {
|
||||
return;
|
||||
}
|
||||
Object.assign(this.$tRefs.content.style, {
|
||||
maxWidth: `${availableWidth}px`,
|
||||
maxHeight: `${availableHeight}px`,
|
||||
});
|
||||
},
|
||||
}));
|
||||
}
|
||||
middleware.push(arrow({ element: this.$tRefs.arrow }));
|
||||
|
||||
this.position = await computePosition(this.$tRefs.reference, this.$tRefs.floating, {
|
||||
middleware,
|
||||
placement: this.placement as Placement,
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.popover {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: .85rem;
|
||||
z-index: 999;
|
||||
}
|
||||
.popover-arrow {
|
||||
position: absolute;
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
</style>
|
@ -1,13 +1,15 @@
|
||||
<template>
|
||||
<a
|
||||
:class="['mr-2', !pronunciation ? 'disabled' : '']"
|
||||
dir="ltr"
|
||||
:href="pronunciationLink"
|
||||
target="_blank"
|
||||
@click.prevent="pronounce"
|
||||
>
|
||||
<Icon :v="icon" /><sub v-if="name">{{ name }}</sub>
|
||||
</a>
|
||||
<Tooltip :text="$t('pronunciation.play')">
|
||||
<a
|
||||
:class="['btn', 'btn-sm', 'btn-link', 'p-1', !pronunciation ? 'disabled' : '']"
|
||||
dir="ltr"
|
||||
:href="pronunciationLink"
|
||||
target="_blank"
|
||||
@click.prevent="pronounce"
|
||||
>
|
||||
<Icon :v="icon" /><sub v-if="name">{{ name }}</sub>
|
||||
</a>
|
||||
</Tooltip>
|
||||
</template>
|
||||
|
||||
|
||||
|
@ -1,152 +1,22 @@
|
||||
<template>
|
||||
<span
|
||||
ref="reference"
|
||||
<Popover
|
||||
:title="text"
|
||||
:aria-label="text"
|
||||
@mouseenter="show"
|
||||
@mouseleave="hide"
|
||||
@touchstart="visible ? hide() : show()"
|
||||
><slot></slot><span
|
||||
v-if="visible"
|
||||
ref="floating"
|
||||
class="tooltip-content bg-dark text-white px-2 py-1 rounded"
|
||||
:style="floatingStyles"
|
||||
>{{ text }}<span ref="arrow" class="tooltip-arrow bg-dark text-white" :style="arrowStyles"></span></span></span>
|
||||
placement="top"
|
||||
>
|
||||
<slot></slot>
|
||||
<template #content>
|
||||
{{ text }}
|
||||
</template>
|
||||
</Popover>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { arrow, autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom';
|
||||
import type { ComputePositionReturn } from '@floating-ui/dom';
|
||||
import type { CSSProperties } from 'vue/types/jsx';
|
||||
|
||||
const getDPR = (element: Element): number => {
|
||||
if (typeof window === 'undefined') {
|
||||
return 1;
|
||||
}
|
||||
const win = element.ownerDocument.defaultView || window;
|
||||
return win.devicePixelRatio || 1;
|
||||
};
|
||||
|
||||
const roundByDPR = (element: Element, value: number): number => {
|
||||
const dpr = getDPR(element);
|
||||
return Math.round(value * dpr) / dpr;
|
||||
};
|
||||
|
||||
const remToPx = (valueInRem: number): number => {
|
||||
return valueInRem * parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||
};
|
||||
|
||||
interface TooltipData {
|
||||
visible: boolean;
|
||||
position: ComputePositionReturn | null;
|
||||
cleanup: (() => void) | null;
|
||||
}
|
||||
|
||||
interface TooltipRefs {
|
||||
reference: HTMLSpanElement | undefined;
|
||||
floating: HTMLSpanElement | undefined;
|
||||
arrow: HTMLSpanElement | undefined;
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
text: { required: true, type: String },
|
||||
},
|
||||
data(): TooltipData {
|
||||
return {
|
||||
visible: false,
|
||||
position: null,
|
||||
cleanup: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
$tRefs(): TooltipRefs {
|
||||
return this.$refs as unknown as TooltipRefs;
|
||||
},
|
||||
floatingStyles(): CSSProperties | undefined {
|
||||
if (!this.position || !this.$tRefs.floating) {
|
||||
return;
|
||||
}
|
||||
const xVal = roundByDPR(this.$tRefs.floating, this.position.x);
|
||||
const yVal = roundByDPR(this.$tRefs.floating, this.position.y);
|
||||
return {
|
||||
transform: `translate(${xVal}px, ${yVal}px)`,
|
||||
...getDPR(this.$tRefs.floating) >= 1.5 && {
|
||||
willChange: 'transform',
|
||||
},
|
||||
};
|
||||
},
|
||||
arrowStyles(): CSSProperties | undefined {
|
||||
if (!this.position) {
|
||||
return;
|
||||
}
|
||||
|
||||
const arrowData = this.position.middlewareData.arrow;
|
||||
|
||||
const staticSite = {
|
||||
top: 'bottom',
|
||||
right: 'left',
|
||||
bottom: 'top',
|
||||
left: 'right',
|
||||
}[this.position.placement.split('-')[0]]!;
|
||||
|
||||
return {
|
||||
left: typeof arrowData?.x === 'number' ? `${arrowData.x}px` : '',
|
||||
top: typeof arrowData?.y === 'number' ? `${arrowData.y}px` : '',
|
||||
[staticSite]: '-0.25rem',
|
||||
};
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.hide();
|
||||
},
|
||||
methods: {
|
||||
async show() {
|
||||
this.visible = true;
|
||||
// floating element will be rendered on next tick
|
||||
await this.$nextTick();
|
||||
if (!this.$tRefs.reference || !this.$tRefs.floating) {
|
||||
return;
|
||||
}
|
||||
// remove title from reference element to prevent a double tooltip
|
||||
this.$tRefs.reference.removeAttribute('title');
|
||||
this.cleanup = autoUpdate(this.$tRefs.reference, this.$tRefs.floating, () => this.update());
|
||||
},
|
||||
hide() {
|
||||
this.visible = false;
|
||||
if (this.cleanup) {
|
||||
this.cleanup();
|
||||
this.cleanup = null;
|
||||
}
|
||||
},
|
||||
async update() {
|
||||
if (!this.$tRefs.reference || !this.$tRefs.floating || !this.$tRefs.arrow) {
|
||||
return;
|
||||
}
|
||||
this.position = await computePosition(this.$tRefs.reference, this.$tRefs.floating, {
|
||||
middleware: [offset(remToPx(0.25)), flip(), shift(), arrow({ element: this.$tRefs.arrow })],
|
||||
placement: 'top',
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.tooltip-content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: .85rem;
|
||||
}
|
||||
.tooltip-arrow {
|
||||
position: absolute;
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
</style>
|
||||
|
@ -63,7 +63,10 @@ home:
|
||||
pronouns:
|
||||
header: 'Pronouns'
|
||||
headerLong: 'Pronouns'
|
||||
examples: 'Example usage in sentences'
|
||||
examples:
|
||||
header: 'Example usage in sentences'
|
||||
shuffle: 'Show different example'
|
||||
shuffleNamed: 'Show different example for %category%'
|
||||
plural: 'Plural'
|
||||
intro: 'My pronouns are'
|
||||
normative: 'Normative'
|
||||
@ -91,6 +94,9 @@ pronouns:
|
||||
or: 'or'
|
||||
grammarTable: 'Table'
|
||||
|
||||
pronunciation:
|
||||
play: 'Play audio sample'
|
||||
|
||||
sources:
|
||||
header: 'Sources'
|
||||
headerLong: 'Examples from cultural texts'
|
||||
|
@ -58,7 +58,8 @@ home:
|
||||
pronouns:
|
||||
header: 'الضمائر'
|
||||
headerLong: 'الضمائر'
|
||||
examples: 'تُستعمل في الجمل كالآتي'
|
||||
examples:
|
||||
header: 'تُستعمل في الجمل كالآتي'
|
||||
plural: 'صيغة الجمع'
|
||||
intro: 'ضمائري هي'
|
||||
normative: 'المعياريه '
|
||||
|
@ -90,6 +90,10 @@ export interface PronounsConfig {
|
||||
* whether pronouns exists which are considered honorifics and thus alter example sentences
|
||||
*/
|
||||
honorifics: boolean;
|
||||
/**
|
||||
* how to group example sentences. if not present, all example sentences are shown
|
||||
*/
|
||||
exampleCategories?: ExampleCategory[];
|
||||
multiple: MultiplePronounsConfig;
|
||||
/**
|
||||
* whether null pronouns are explained or can be generated.
|
||||
@ -143,6 +147,22 @@ export interface PronounsConfig {
|
||||
disableDisclaimer?: boolean;
|
||||
}
|
||||
|
||||
interface ExampleCategory {
|
||||
/**
|
||||
* short description of the category (translated)
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* which morphemes belong to this category. visible example sentences will contain at least one listed morpheme
|
||||
*/
|
||||
morphemes: string[];
|
||||
/**
|
||||
* whether this category is only shown in comprehensive view
|
||||
* @default false
|
||||
*/
|
||||
comprehensive?: boolean;
|
||||
}
|
||||
|
||||
interface MultiplePronounsConfig {
|
||||
/**
|
||||
* short description in the header (translated)
|
||||
|
@ -10,6 +10,79 @@ pronouns:
|
||||
comprehensive: 'erweitert'
|
||||
plurals: true
|
||||
honorifics: false
|
||||
exampleCategories:
|
||||
-
|
||||
name: 'Personalpronomen im Nominativ'
|
||||
morphemes: ['pronoun_n']
|
||||
-
|
||||
name: 'Possessiv'
|
||||
morphemes:
|
||||
- 'possessive_determiner_f_n'
|
||||
- 'possessive_determiner_f_g'
|
||||
- 'possessive_determiner_f_a'
|
||||
- 'possessive_determiner_f_d'
|
||||
- 'possessive_determiner_m_n'
|
||||
- 'possessive_determiner_m_g'
|
||||
- 'possessive_determiner_m_a'
|
||||
- 'possessive_determiner_m_d'
|
||||
- 'possessive_determiner_n_n'
|
||||
- 'possessive_determiner_n_g'
|
||||
- 'possessive_determiner_n_a'
|
||||
- 'possessive_determiner_n_d'
|
||||
- 'possessive_determiner_x_n'
|
||||
- 'possessive_determiner_x_g'
|
||||
- 'possessive_determiner_x_a'
|
||||
- 'possessive_determiner_x_d'
|
||||
- 'possessive_determiner_pl_n'
|
||||
- 'possessive_determiner_pl_g'
|
||||
- 'possessive_determiner_pl_a'
|
||||
- 'possessive_determiner_pl_d'
|
||||
- 'possessive_pronoun_f_n'
|
||||
- 'possessive_pronoun_f_g'
|
||||
- 'possessive_pronoun_f_a'
|
||||
- 'possessive_pronoun_f_d'
|
||||
- 'possessive_pronoun_m_n'
|
||||
- 'possessive_pronoun_m_g'
|
||||
- 'possessive_pronoun_m_a'
|
||||
- 'possessive_pronoun_m_d'
|
||||
- 'possessive_pronoun_n_n'
|
||||
- 'possessive_pronoun_n_g'
|
||||
- 'possessive_pronoun_n_a'
|
||||
- 'possessive_pronoun_n_d'
|
||||
- 'possessive_pronoun_x_n'
|
||||
- 'possessive_pronoun_x_g'
|
||||
- 'possessive_pronoun_x_a'
|
||||
- 'possessive_pronoun_x_d'
|
||||
- 'possessive_pronoun_pl_n'
|
||||
- 'possessive_pronoun_pl_g'
|
||||
- 'possessive_pronoun_pl_a'
|
||||
- 'possessive_pronoun_pl_d'
|
||||
-
|
||||
name: 'Personalpronomen im Dativ'
|
||||
morphemes: ['pronoun_d']
|
||||
-
|
||||
name: 'Personalpronomen im Akkusativ'
|
||||
morphemes: ['pronoun_a']
|
||||
-
|
||||
name: 'Personalpronomen im Genitiv'
|
||||
morphemes: ['pronoun_g']
|
||||
comprehensive: true
|
||||
-
|
||||
name: 'Relativpronomen'
|
||||
morphemes: ['relative_n', 'relative_g', 'relative_d', 'relative_a']
|
||||
comprehensive: true
|
||||
-
|
||||
name: 'Demonstrativpronomen'
|
||||
morphemes: ['demonstrative_n', 'demonstrative_g', 'demonstrative_d', 'demonstrative_a']
|
||||
comprehensive: true
|
||||
-
|
||||
name: 'weitere Pronomen und Adjektive'
|
||||
morphemes: ['pronoun_equal', 'possessive_pronoun_substantivized', 'adjective_back_then']
|
||||
comprehensive: true
|
||||
-
|
||||
name: 'Adverbien'
|
||||
morphemes: ['adverb_because', 'adverb_back_then', 'adverb_by']
|
||||
comprehensive: true
|
||||
multiple:
|
||||
name: 'Austauschbare Pronomen'
|
||||
description: 'Einige nichtbinäre Menschen nutzen mehr als eine Form von Pronomen und sind damit einverstanden, mit irgendwelchen dieser Pronomen angesprochen zu werden.'
|
||||
|
@ -49,10 +49,11 @@
|
||||
</th>
|
||||
</template>
|
||||
</template>
|
||||
<td v-for="morpheme in variant.morphemes" :key="morpheme">
|
||||
<td v-for="morphemeCell in variant.morphemeCells" :key="morphemeCell.morpheme">
|
||||
<MorphemeWithPronunciation
|
||||
:pronoun="selectedPronoun"
|
||||
:morpheme="morpheme"
|
||||
:morpheme="morphemeCell.morpheme"
|
||||
:highlights-morphemes="morphemeCell.highlightsMorphemes"
|
||||
:counter="counter"
|
||||
/>
|
||||
</td>
|
||||
@ -121,7 +122,22 @@ interface Header {
|
||||
|
||||
interface SectionDefinition {
|
||||
header?: Header;
|
||||
variants: PronounVariant[] | { base: string, type: string };
|
||||
variants: PronounVariantDefinition[] | PronounVariantsFromDeclensionDefinition;
|
||||
}
|
||||
|
||||
interface PronounVariantDefinition {
|
||||
name?: string;
|
||||
morphemeCells: (string | MorphemeCellDefinition)[];
|
||||
}
|
||||
|
||||
interface PronounVariantsFromDeclensionDefinition {
|
||||
base: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface MorphemeCellDefinition {
|
||||
morpheme: string;
|
||||
highlightsMorphemes: string[];
|
||||
}
|
||||
|
||||
const grammarTablesDefinitions: Record<'simple' | 'comprehensive', GrammarTableDefinition[]> = {
|
||||
@ -149,7 +165,21 @@ const grammarTablesDefinitions: Record<'simple' | 'comprehensive', GrammarTableD
|
||||
{
|
||||
variants: [
|
||||
{
|
||||
morphemes: ['pronoun_n', 'possessive_determiner_m_n', 'pronoun_d', 'pronoun_a'],
|
||||
morphemeCells: [
|
||||
'pronoun_n',
|
||||
{
|
||||
morpheme: 'possessive_determiner_m_n',
|
||||
highlightsMorphemes: ['determiner', 'pronoun'].flatMap((possessiveKind) => {
|
||||
return ['f', 'm', 'n', 'x', 'pl'].flatMap((genus) => {
|
||||
return cases.flatMap((casus) => {
|
||||
return `possessive_${possessiveKind}_${genus}_${casus}`;
|
||||
});
|
||||
});
|
||||
}),
|
||||
},
|
||||
'pronoun_d',
|
||||
'pronoun_a',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -245,11 +275,11 @@ const grammarTablesDefinitions: Record<'simple' | 'comprehensive', GrammarTableD
|
||||
variants: [
|
||||
{
|
||||
name: 'gleicher Art',
|
||||
morphemes: ['pronoun_equal'],
|
||||
morphemeCells: ['pronoun_equal'],
|
||||
},
|
||||
{
|
||||
name: 'zugehörig',
|
||||
morphemes: ['possessive_pronoun_substantivized'],
|
||||
morphemeCells: ['possessive_pronoun_substantivized'],
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -261,15 +291,15 @@ const grammarTablesDefinitions: Record<'simple' | 'comprehensive', GrammarTableD
|
||||
variants: [
|
||||
{
|
||||
name: 'wegen',
|
||||
morphemes: ['adverb_because'],
|
||||
morphemeCells: ['adverb_because'],
|
||||
},
|
||||
{
|
||||
name: 'damals',
|
||||
morphemes: ['adverb_back_then'],
|
||||
morphemeCells: ['adverb_back_then'],
|
||||
},
|
||||
{
|
||||
name: 'von',
|
||||
morphemes: ['adverb_by'],
|
||||
morphemeCells: ['adverb_by'],
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -280,7 +310,7 @@ const grammarTablesDefinitions: Record<'simple' | 'comprehensive', GrammarTableD
|
||||
},
|
||||
variants: [
|
||||
{
|
||||
morphemes: ['adjective_back_then'],
|
||||
morphemeCells: ['adjective_back_then'],
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -289,8 +319,8 @@ const grammarTablesDefinitions: Record<'simple' | 'comprehensive', GrammarTableD
|
||||
],
|
||||
};
|
||||
|
||||
const morphemesByCase = (morphemeBase: string): string[] => {
|
||||
return cases.map((caseAbbreviation) => `${morphemeBase}_${caseAbbreviation}`);
|
||||
const morphemesByCase = (morphemeBase: string): MorphemeCell[] => {
|
||||
return cases.map((caseAbbreviation) => ({ morpheme: `${morphemeBase}_${caseAbbreviation}` }));
|
||||
};
|
||||
|
||||
interface GrammarTable {
|
||||
@ -308,23 +338,36 @@ interface PronounVariant {
|
||||
name?: string;
|
||||
numerus?: 'singular' | 'plural';
|
||||
icon?: string;
|
||||
morphemes: string[];
|
||||
morphemeCells: MorphemeCell[];
|
||||
}
|
||||
|
||||
interface MorphemeCell {
|
||||
morpheme: string;
|
||||
highlightsMorphemes?: Set<string>;
|
||||
}
|
||||
|
||||
const expandVariantsForSection = (sectionVariants: SectionDefinition['variants']): PronounVariant[] => {
|
||||
if (Array.isArray(sectionVariants)) {
|
||||
return sectionVariants;
|
||||
return sectionVariants.map((sectionVariant) => {
|
||||
const morphemeCells = sectionVariant.morphemeCells.map((morphemeCell) => {
|
||||
if (typeof morphemeCell === 'string') {
|
||||
return { morpheme: morphemeCell };
|
||||
}
|
||||
return { ...morphemeCell, highlightsMorphemes: new Set(morphemeCell.highlightsMorphemes) };
|
||||
});
|
||||
return { ...sectionVariant, morphemeCells };
|
||||
});
|
||||
} else {
|
||||
const morphemeBase = sectionVariants.base;
|
||||
switch (sectionVariants.type) {
|
||||
case 'case':
|
||||
return [{ morphemes: morphemesByCase(morphemeBase) }];
|
||||
return [{ morphemeCells: morphemesByCase(morphemeBase) }];
|
||||
case 'declension-with-case':
|
||||
return declensions.map((declension) => ({
|
||||
name: declension.name,
|
||||
numerus: declension.numerus,
|
||||
icon: declension.icon,
|
||||
morphemes: morphemesByCase(`${morphemeBase}_${declension.abbreviation}`),
|
||||
morphemeCells: morphemesByCase(`${morphemeBase}_${declension.abbreviation}`),
|
||||
}));
|
||||
default:
|
||||
throw new Error(`variant type ${sectionVariants.type} is unknown`);
|
||||
@ -338,7 +381,9 @@ const buildVariantsForSection = (
|
||||
counter: number,
|
||||
): PronounVariant[] => {
|
||||
return expandVariantsForSection(sectionVariants).filter((variant) => {
|
||||
return variant.morphemes.some((morpheme) => pronoun.getMorpheme(morpheme, counter) !== null);
|
||||
return variant.morphemeCells.some((morphemeCell) => {
|
||||
return pronoun.getMorpheme(morphemeCell.morpheme, counter) !== null;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1,17 +1,63 @@
|
||||
singular plural isHonorific comprehensive
|
||||
{'pronoun_n} ist so süß. {'pronoun_n} sind so süß. FALSE FALSE
|
||||
Ist das {possessive_determiner_m_n} Hund? FALSE FALSE
|
||||
Heute hat {pronoun_n} {possessive_determiner_m_a} Apfel, {possessive_determiner_f_a} Birne und {possessive_determiner_n_a} Törtchen dabei. Heute haben {pronoun_n} {possessive_determiner_m_a} Apfel, {possessive_determiner_f_a} Birne und {possessive_determiner_n_a} Törtchen dabei. FALSE TRUE
|
||||
{'possessive_determiner_n_n} Kaninchen spielt mit {possessive_determiner_m_d} Hund. FALSE TRUE
|
||||
Meine Lieblingsfarbe ist violett, {possessive_pronoun_f_n} ist gelb. FALSE TRUE
|
||||
Dieses Haus ist recht alt, während {possessive_pronoun_n_n} gerade renoviert wurde. FALSE TRUE
|
||||
Wir freuen uns {pronoun_g}. FALSE TRUE
|
||||
Ich bin {pronoun_d} erst kürzlich begegnet. FALSE FALSE
|
||||
Ich verstehe {pronoun_a} so gut. FALSE FALSE
|
||||
{'relative_n} Einzige, {relative_a} ich kenne, {relative_d} wir noch einen Gefallen schulden. {'relative_n} Einzigen, {relative_a} ich kenne, {relative_d} wir noch einen Gefallen schulden. FALSE TRUE
|
||||
{'demonstrative_n} Studierende kennt alle Lösungen {possessive_determiner_f_g} Übungsaufgabe. {'demonstrative_n} Studierende kennen alle Lösungen {possessive_determiner_f_g} Übungsaufgabe. FALSE TRUE
|
||||
Mit {pronoun_equal} komme ich gut zurecht. FALSE TRUE
|
||||
Mein Fahrrad ist schneller als das {possessive_pronoun_substantivized}. FALSE TRUE
|
||||
{'pronoun_n} möchte nicht, dass wir nur {adverb_because} einen Umweg fahren. {'pronoun_n} möchten nicht, dass wir nur {adverb_because} einen Umweg fahren. FALSE TRUE
|
||||
{'adverb_back_then} wurden {possessive_determiner_pl_n} Ideen kontrovers diskutiert. FALSE TRUE
|
||||
Mit {possessive_determiner_f_d} großer Geduld reagiert {pronoun_n} {adverb_by} gelassen auf die unruhige Situation. Mit {possessive_determiner_f_d} großer Geduld reagieren {pronoun_n} {adverb_by} gelassen auf die unruhige Situation. FALSE TRUE
|
||||
singular plural
|
||||
{'pronoun_n} ist so süß. {'pronoun_n} sind so süß.
|
||||
Ist das {possessive_determiner_m_n} Hund?
|
||||
Ich bin {pronoun_d} erst kürzlich begegnet.
|
||||
Ich verstehe {pronoun_a} so gut.
|
||||
Darum kümmert sich {pronoun_n}. Darum kümmern sich {pronoun_n}.
|
||||
{'pronoun_n} hat das Telefon abgehoben. {'pronoun_n} haben das Telefon abgehoben.
|
||||
{'pronoun_n} spricht während {pronoun_n} schläft. {'pronoun_n} sprechen während {pronoun_n} schläft.
|
||||
Wir nehmen uns {pronoun_g} an.
|
||||
Wir erfreuen uns {pronoun_g}.
|
||||
Hast du Lust, mit {pronoun_d} ins Kino zu gehen?
|
||||
Ich habe gestern mit {pronoun_d} gesprochen.
|
||||
Ich bin mit {pronoun_d} zur Schule gegangen.
|
||||
Ich habe {pronoun_a} gestern getroffen.
|
||||
Wir haben das letzte Mal über {pronoun_a} gesprochen.
|
||||
{'pronoun_a} kenne ich schon lange.
|
||||
Kannst du {pronoun_d} Bescheid geben, wenn {possessive_determiner_f_n} Katze aufwacht?
|
||||
{'possessive_determiner_f_n} Abschlussfeier beginnt bald.
|
||||
{'pronoun_n} kümmert sich sehr gut um {possessive_determiner_f_a} Katze. {'pronoun_n} kümmern sich sehr gut um {possessive_determiner_f_a} Katze.
|
||||
Ich habe {pronoun_a} gefragt, ob ich {possessive_determiner_m_a} Bleistift ausleihen kann.
|
||||
Heute hat {pronoun_n} {possessive_determiner_m_a} Apfel, {possessive_determiner_f_a} Birne und {possessive_determiner_n_a} Törtchen dabei. Heute haben {pronoun_n} {possessive_determiner_m_a} Apfel, {possessive_determiner_f_a} Birne und {possessive_determiner_n_a} Törtchen dabei.
|
||||
{'possessive_determiner_n_n} Kaninchen spielt mit {possessive_determiner_m_d} Hund.
|
||||
Das ist das Ladekabel {possessive_determiner_m_g} Computers.
|
||||
Der Stall {possessive_determiner_n_g} Pferdes wird von {pronoun_d} wöchentlich besucht.
|
||||
Mit {possessive_determiner_n_d} Fahrrad kommt {pronoun_n} sicher ans Ziel. Mit {possessive_determiner_n_d} Fahrrad kommen {pronoun_n} sicher ans Ziel.
|
||||
Ich vermisse {possessive_determiner_n_a} Lachen.
|
||||
{'possessive_determiner_x_n} Freund*in besucht {pronoun_a} morgen.
|
||||
Kennst du die Telefonnummer {possessive_determiner_x_g} Ärzt*in?
|
||||
{'pronoun_n} bringt {possessive_determiner_x_d} Partner*in ein Geschenk.
|
||||
Hast du {possessive_determiner_x_a} Freund*in gesehen?
|
||||
{'possessive_determiner_pl_n} Mitarbeitenden sind hilfsbereit.
|
||||
Den Umfang {possessive_determiner_pl_g} Aufgaben kann {pronoun_n} leicht bewältigen.
|
||||
Bringe bitte {possessive_determiner_pl_d} Haustiere frisches Futter mit.
|
||||
Wir schätzen {possessive_determiner_pl_a} Ideen.
|
||||
Meine Lieblingsfarbe ist violett, {possessive_pronoun_f_n} ist gelb.
|
||||
Ich hätte gerne eine Schildkröte, die so aussieht wie {possessive_pronoun_f_n}.
|
||||
Es bedarf meiner Hilfe, nicht {possessive_pronoun_f_g}.
|
||||
Dieses Jahr sind wir in meiner Stadt, nächstes in {possessive_pronoun_f_d}.
|
||||
Ich mag meine Arbeit, während {possessive_pronoun_f_a} mich überfordern würde.
|
||||
{'possessive_determiner_m_a} Geburtstag kann {pronoun_n} kaum erwarten, {possessive_pronoun_m_n} ist aber erst in drei Monaten. {‘possessive_determiner_m_a} Geburtstag können {pronoun_n} kaum erwarten, {possessive_pronoun_m_n} ist aber erst in drei Monaten.
|
||||
Mein Kuchen ist sehr lecker, schmeckt {pronoun_d} auch {possessive_pronoun_m_n}?
|
||||
Wir sind uns unseres Fehlers bewusst, {pronoun_n} sich auch {possessive_pronoun_m_g}?
|
||||
Ich spiele gerne mit meinem Ball, aber gelegentlich auch mit {possessive_pronoun_m_d}.
|
||||
Bei der Suche nach interessanten Artikeln bin ich auf {possessive_pronoun_m_a} gestoßen.
|
||||
{'pronoun_n} hat mir gesagt, dieses Haus ist {possessive_pronoun_n_n}.
|
||||
Versprechen sind einzuhalten, und ich erinnere mich {possessive_pronoun_n_g}.
|
||||
Du trinkst von deinem Glas und {pronoun_n} von {possessive_pronoun_n_d}.
|
||||
Du kaufst dein neues Outfit und {pronoun_n} {possessive_pronoun_n_a}.
|
||||
Meine Eltern unterstützen {pronoun_a}. Und {possessive_pronoun_pl_n}?
|
||||
Wir brauchen noch Geschirr für das Picknick, also bedienen wir uns {possessive_pronoun_pl_g}.
|
||||
Es ist wichtig Kontakt mit Bekannten zu halten, also schreibt {pronoun_n} häufig {possessive_pronoun_pl_d}. Es ist wichtig Kontakt mit Bekannten zu halten, also schreiben {pronoun_n} häufig {possessive_pronoun_pl_d}.
|
||||
Heute sind die Bücher angekommen, bitte gebe {pronoun_d} {possessive_pronoun_pl_a}.
|
||||
{'relative_n} Einzige, {relative_a} ich kenne, {relative_d} wir noch einen Gefallen schulden. {'relative_n} Einzigen, {relative_a} ich kenne, {relative_d} wir noch einen Gefallen schulden.
|
||||
{'demonstrative_n} Studierende kennt alle Lösungen {possessive_determiner_f_g} Übungsaufgabe. {'demonstrative_n} Studierende kennen alle Lösungen {possessive_determiner_f_g} Übungsaufgabe.
|
||||
{‘demonstrative_n} drüben ist hübsch. {‘demonstrative_n} drüben sind hübsch.
|
||||
Gib das einfach {demonstrative_d} da.
|
||||
Mit {pronoun_equal} komme ich gut zurecht.
|
||||
Mein Fahrrad ist schneller als das {possessive_pronoun_substantivized}.
|
||||
{'pronoun_n} möchte nicht, dass wir nur {adverb_because} einen Umweg fahren. {'pronoun_n} möchten nicht, dass wir nur {adverb_because} einen Umweg fahren.
|
||||
{'adverb_because} sollen wir nicht auf {pronoun_a} warten.
|
||||
{'adverb_back_then} wurden {possessive_determiner_pl_n} Ideen kontrovers diskutiert.
|
||||
Mit {possessive_determiner_f_d} großer Geduld reagiert {pronoun_n} {adverb_by} gelassen auf die unruhige Situation. Mit {possessive_determiner_f_d} großer Geduld reagieren {pronoun_n} {adverb_by} gelassen auf die unruhige Situation.
|
||||
Das {adjective_back_then}e Motto lautete: „Wir schaffen das!“
|
||||
|
|
@ -61,7 +61,10 @@ home:
|
||||
pronouns:
|
||||
header: 'Pronomen'
|
||||
headerLong: 'Pronomen'
|
||||
examples: 'Beispielsätze'
|
||||
examples:
|
||||
header: 'Beispielsätze'
|
||||
shuffle: 'Anderen Beispielsatz anzeigen'
|
||||
shuffleNamed: 'Anderen Beispielsatz für %category% anzeigen'
|
||||
plural: 'Plural'
|
||||
intro: 'Meine Pronomen sind'
|
||||
normative: 'Normativ'
|
||||
@ -94,6 +97,9 @@ pronouns:
|
||||
or: 'oder'
|
||||
grammarTable: 'Tabelle'
|
||||
|
||||
pronunciation:
|
||||
play: 'Hörbeispiel abspielen'
|
||||
|
||||
sources:
|
||||
header: 'Quellen'
|
||||
headerLong: 'Beispiele aus Kulturtexten'
|
||||
|
@ -11,6 +11,22 @@ pronouns:
|
||||
any: 'any'
|
||||
plurals: true
|
||||
honorifics: false
|
||||
exampleCategories:
|
||||
-
|
||||
name: 'subject pronoun'
|
||||
morphemes: ['pronoun_subject']
|
||||
-
|
||||
name: 'object pronoun'
|
||||
morphemes: ['pronoun_object']
|
||||
-
|
||||
name: 'possessive determiner'
|
||||
morphemes: ['possessive_determiner']
|
||||
-
|
||||
name: 'possessive pronoun'
|
||||
morphemes: ['possessive_pronoun']
|
||||
-
|
||||
name: 'reflexive pronoun'
|
||||
morphemes: ['reflexive']
|
||||
multiple:
|
||||
name: 'Interchangeable forms'
|
||||
description: >
|
||||
|
@ -1,5 +1,20 @@
|
||||
singular plural isHonorific
|
||||
I think {pronoun_subject} is very nice. I think {pronoun_subject} are very nice. FALSE
|
||||
I asked {pronoun_object} if I can borrow {possessive_determiner} pencil. FALSE
|
||||
{'pronoun_subject} told me that the house is {possessive_pronoun}. FALSE
|
||||
{'pronoun_subject} said {pronoun_subject} would rather do it {reflexive}. FALSE
|
||||
singular plural
|
||||
I think {pronoun_subject} is very nice. I think {pronoun_subject} are very nice.
|
||||
I met {pronoun_object} recently.
|
||||
Is this {possessive_determiner} dog?
|
||||
{'pronoun_subject} told me that the house is {possessive_pronoun}.
|
||||
{'pronoun_subject} said {pronoun_subject} would rather do it {reflexive}.
|
||||
{'pronoun_subject} is really beautiful. {'pronoun_subject} are really beautiful.
|
||||
{'pronoun_subject} answered the phone.
|
||||
{'pronoun_subject} takes good care of {possessive_determiner} cat. {'pronoun_subject} take good care of {possessive_determiner} cat.
|
||||
{'pronoun_subject} did it all by {reflexive}.
|
||||
{'pronoun_subject} talks in {possessive_determiner} sleep. {'pronoun_subject} talk in {possessive_determiner} sleep.
|
||||
{'pronoun_subject} landed the plane safely.
|
||||
{'pronoun_subject} argues that… {'pronoun_subject} argue that…
|
||||
Did you buy {pronoun_object} {possessive_determiner} gift?
|
||||
I asked {pronoun_object} if I can borrow {possessive_determiner} pencil.
|
||||
I talked to {pronoun_object} yesterday.
|
||||
Would you like to go to the movies with {pronoun_object}?
|
||||
Can you call {pronoun_object} when {possessive_determiner} cat awakes?
|
||||
{'possessive_determiner} graduation starts soon.
|
||||
My favorite color is purple, {possessive_pronoun} is yellow.
|
||||
|
|
@ -63,7 +63,10 @@ home:
|
||||
pronouns:
|
||||
header: 'Pronouns'
|
||||
headerLong: 'Pronouns'
|
||||
examples: 'Example usage in sentences'
|
||||
examples:
|
||||
header: 'Example usage in sentences'
|
||||
shuffle: 'Show different example'
|
||||
shuffleNamed: 'Show different example for %category%'
|
||||
plural: 'Plural'
|
||||
intro: 'My pronouns are'
|
||||
normative: 'Normative'
|
||||
@ -89,6 +92,9 @@ pronouns:
|
||||
sentence: >
|
||||
You can modify the default links so that the URL reads like a sentence; for example:
|
||||
|
||||
pronunciation:
|
||||
play: 'Play audio sample'
|
||||
|
||||
sources:
|
||||
header: 'Sources'
|
||||
headerLong: 'Examples from cultural texts'
|
||||
|
@ -50,7 +50,8 @@ home:
|
||||
pronouns:
|
||||
header: 'Pronomoj'
|
||||
headerLong: 'Pronomoj'
|
||||
examples: 'Ekzempla uzado en frazoj'
|
||||
examples:
|
||||
header: 'Ekzempla uzado en frazoj'
|
||||
plural: 'Pluralo'
|
||||
intro: 'Miaj pronomoj estas'
|
||||
normative: 'Norma'
|
||||
|
@ -61,7 +61,8 @@ home:
|
||||
pronouns:
|
||||
header: 'Pronombres'
|
||||
headerLong: 'Pronombres'
|
||||
examples: 'Ejemplos de uso en oraciones'
|
||||
examples:
|
||||
header: 'Ejemplos de uso en oraciones'
|
||||
plural: 'Plural'
|
||||
intro: 'Mis pronombres son'
|
||||
normative: 'Normativo'
|
||||
|
@ -56,7 +56,8 @@ home:
|
||||
pronouns:
|
||||
header: 'Asesõnad'
|
||||
headerLong: 'Asesõnad'
|
||||
examples: 'Kasutuse näide lausetes'
|
||||
examples:
|
||||
header: 'Kasutuse näide lausetes'
|
||||
plural: 'Mitmus'
|
||||
intro: 'Minu asesõnad on'
|
||||
normative: 'Normatiivne'
|
||||
|
@ -58,7 +58,8 @@ home:
|
||||
pronouns:
|
||||
header: 'Pronoms'
|
||||
headerLong: 'Pronoms'
|
||||
examples: 'Exemple d’usage dans des phrases'
|
||||
examples:
|
||||
header: 'Exemple d’usage dans des phrases'
|
||||
plural: 'Pluriel'
|
||||
intro: 'Mes pronoms sont'
|
||||
normative: 'Normatif'
|
||||
|
@ -60,7 +60,8 @@ home:
|
||||
pronouns:
|
||||
header: 'Pronomes'
|
||||
headerLong: 'Pronomes'
|
||||
examples: 'Exemplos de uso em frases'
|
||||
examples:
|
||||
header: 'Exemplos de uso em frases'
|
||||
plural: 'Plural'
|
||||
intro: 'Meus pronomes são'
|
||||
normative: 'Normativo'
|
||||
|
@ -62,7 +62,8 @@ home:
|
||||
pronouns:
|
||||
header: 'Pronouns'
|
||||
headerLong: 'Pronouns'
|
||||
examples: 'Example usage in sentences'
|
||||
examples:
|
||||
header: 'Example usage in sentences'
|
||||
plural: 'Plural'
|
||||
intro: 'My pronouns are'
|
||||
normative: 'Normative'
|
||||
|
@ -60,7 +60,8 @@ home:
|
||||
pronouns:
|
||||
header: 'Pronomi'
|
||||
headerLong: 'Pronomi'
|
||||
examples: 'Esempi pratici di utilizzo'
|
||||
examples:
|
||||
header: 'Esempi pratici di utilizzo'
|
||||
plural: 'Plurale'
|
||||
intro: 'I miei pronomi sono'
|
||||
normative: 'Normative'
|
||||
|
@ -44,7 +44,8 @@ home:
|
||||
pronouns:
|
||||
header: '三人称代名詞'
|
||||
headerLong: '三人称代名詞'
|
||||
examples: '文章での使い方の例'
|
||||
examples:
|
||||
header: '文章での使い方の例'
|
||||
plural: '複数形'
|
||||
intro: '私の三人称代名詞は'
|
||||
normative: '規範的'
|
||||
|
@ -61,7 +61,8 @@ home:
|
||||
pronouns:
|
||||
header: '대명사'
|
||||
headerLong: '대명사'
|
||||
examples: '문장안에 예'
|
||||
examples:
|
||||
header: '문장안에 예'
|
||||
plural: '복유의'
|
||||
intro: '나의 대명사는'
|
||||
normative: '규범적'
|
||||
|
@ -61,7 +61,8 @@ home:
|
||||
pronouns:
|
||||
header: 'Pronombres'
|
||||
headerLong: 'Pronombres'
|
||||
examples: 'Enshemplos de uzo en frazas'
|
||||
examples:
|
||||
header: 'Enshemplos de uzo en frazas'
|
||||
plural: 'Plural'
|
||||
intro: 'Mis pronombres son'
|
||||
normative: 'Normativo'
|
||||
|
@ -59,7 +59,8 @@ home:
|
||||
pronouns:
|
||||
header: 'Voornaamwoorden'
|
||||
headerLong: 'Voornaamwoorden'
|
||||
examples: 'Gebruiksvoorbeelden in zinnen'
|
||||
examples:
|
||||
header: 'Gebruiksvoorbeelden in zinnen'
|
||||
plural: 'Meervoud'
|
||||
intro: 'Mijn voornaamwoorden zijn'
|
||||
normative: 'Normatief'
|
||||
|
@ -53,7 +53,8 @@ home:
|
||||
pronouns:
|
||||
header: 'Pronomen'
|
||||
headerLong: 'Pronomen'
|
||||
examples: 'Eksempler i setninger'
|
||||
examples:
|
||||
header: 'Eksempler i setninger'
|
||||
plural: 'Flertall'
|
||||
intro: 'Mine pronomen er'
|
||||
normative: 'Normativ'
|
||||
|
@ -74,7 +74,8 @@ home:
|
||||
pronouns:
|
||||
header: 'Zaimki'
|
||||
headerLong: 'Niebinarne zaimki<br/> i inne formy'
|
||||
examples: 'Przykłady użycia w zdaniu'
|
||||
examples:
|
||||
header: 'Przykłady użycia w zdaniu'
|
||||
plural: 'Liczba mnoga'
|
||||
intro: 'Moje zaimki to'
|
||||
normative: 'Normatywne'
|
||||
@ -282,7 +283,8 @@ nouns:
|
||||
Słownik Neutratywów podaje odmianę pasującą do {/ono=„ono/jego”},
|
||||
lecz możliwa jest też odmiana zgodna z pozostałymi opcjami:
|
||||
|
||||
examples: 'Przykłady'
|
||||
examples:
|
||||
header: 'Przykłady'
|
||||
dictionary: 'Słownik Neutratywów'
|
||||
|
||||
approved: 'wpisów zatwierdzonych'
|
||||
|
@ -65,7 +65,8 @@ home:
|
||||
pronouns:
|
||||
header: 'Pronomes'
|
||||
headerLong: 'Pronomes'
|
||||
examples: 'Exemplos de uso em frases'
|
||||
examples:
|
||||
header: 'Exemplos de uso em frases'
|
||||
plural: 'Plural'
|
||||
intro: 'Meu conjunto é'
|
||||
normative: 'Normativo'
|
||||
|
@ -61,7 +61,8 @@ home:
|
||||
pronouns:
|
||||
header: 'Pronume'
|
||||
headerLong: 'Listă cu pronume'
|
||||
examples: 'Utilizarea pronumelor în propoziții'
|
||||
examples:
|
||||
header: 'Utilizarea pronumelor în propoziții'
|
||||
plural: 'Plural'
|
||||
intro: 'Pronumele mele sunt'
|
||||
normative: 'Normativ'
|
||||
|
@ -54,7 +54,8 @@ home:
|
||||
pronouns:
|
||||
header: 'Местоимения'
|
||||
headerLong: 'Местоимения и другие формы'
|
||||
examples: 'Примеры употребления в предложениях'
|
||||
examples:
|
||||
header: 'Примеры употребления в предложениях'
|
||||
plural: 'Множественное число'
|
||||
intro: 'Мои местоимения'
|
||||
normative: 'Нормативное'
|
||||
|
@ -63,7 +63,8 @@ home:
|
||||
pronouns:
|
||||
header: 'Pronomen'
|
||||
headerLong: 'Pronomen'
|
||||
examples: 'Exempel på användningar i meningar'
|
||||
examples:
|
||||
header: 'Exempel på användningar i meningar'
|
||||
plural: 'Plural'
|
||||
intro: 'Mina pronomen är'
|
||||
normative: 'Normativ'
|
||||
|
@ -1,5 +1 @@
|
||||
singular plural isHonorific
|
||||
I think {pronoun_subject} is very nice. I think {pronoun_subject} are very nice. FALSE
|
||||
I asked {pronoun_object} if I can borrow {possessive_determiner} pencil. FALSE
|
||||
{'pronoun_subject} told me that the house is {possessive_pronoun}. FALSE
|
||||
{'pronoun_subject} said {pronoun_subject} would rather do it {reflexive}. FALSE
|
||||
|
|
@ -50,7 +50,8 @@ home:
|
||||
pronouns:
|
||||
header: 'Zamirler'
|
||||
headerLong: 'Zamirler'
|
||||
examples: 'Cumlede nasil gözükeceğini anlamak icin örnek kullanım'
|
||||
examples:
|
||||
header: 'Cumlede nasil gözükeceğini anlamak icin örnek kullanım'
|
||||
plural: 'Birden Çok'
|
||||
intro: 'Benim zamirlerim'
|
||||
normative: 'Genel Zamirler'
|
||||
|
@ -54,7 +54,8 @@ home:
|
||||
pronouns:
|
||||
header: 'Займенники'
|
||||
headerLong: 'Займенники та інші форми'
|
||||
examples: 'Приклади використання у реченнях'
|
||||
examples:
|
||||
header: 'Приклади використання у реченнях'
|
||||
plural: 'Множина'
|
||||
intro: 'Мої займенники'
|
||||
normative: 'Нормативне'
|
||||
|
@ -63,7 +63,8 @@ home:
|
||||
pronouns:
|
||||
header: 'Các danh xưng'
|
||||
headerLong: 'Các danh xưng'
|
||||
examples: 'Ví dụ trong câu'
|
||||
examples:
|
||||
header: 'Ví dụ trong câu'
|
||||
plural: 'Số nhiều'
|
||||
intro: 'Tôi dùng các danh xưng'
|
||||
normative: 'Chuẩn mực'
|
||||
|
@ -60,7 +60,8 @@ home:
|
||||
pronouns:
|
||||
header: 'פּראָנאָמען'
|
||||
headerLong: 'פּראָנאָמען'
|
||||
examples: 'Example usage in sentences'
|
||||
examples:
|
||||
header: 'Example usage in sentences'
|
||||
plural: 'מערצאָל'
|
||||
intro: 'מײַנע פּראָנאָמען זענען'
|
||||
normative: 'נאָרמאַטיװ'
|
||||
|
@ -62,7 +62,8 @@ home:
|
||||
pronouns:
|
||||
header: '代詞'
|
||||
headerLong: '代詞'
|
||||
examples: '例句用法'
|
||||
examples:
|
||||
header: '例句用法'
|
||||
plural: '複數'
|
||||
intro: '我的代詞是'
|
||||
normative: '規範'
|
||||
|
@ -20,10 +20,11 @@
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { head } from '../src/helpers.ts';
|
||||
|
||||
export default {
|
||||
export default Vue.extend({
|
||||
async asyncData({ app }) {
|
||||
return {
|
||||
chart: await app.$axios.$get(`/admin/stats/users-chart/${process.env.LOCALE}`),
|
||||
@ -34,5 +35,5 @@ export default {
|
||||
title: `${this.$t('admin.header')} • Profiles`,
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<Icon v="tag" />
|
||||
<T>pronouns.intro</T><T>quotation.colon</T>
|
||||
</span>
|
||||
<ComprehensiveSwitch @update:comprehensive="updated => comprehensive = updated" />
|
||||
<ComprehensiveSwitch v-model="comprehensive" />
|
||||
</h2>
|
||||
|
||||
<section>
|
||||
@ -29,14 +29,17 @@
|
||||
<section>
|
||||
<h2 class="h4">
|
||||
<Icon v="file-signature" />
|
||||
<T>pronouns.examples</T><T>quotation.colon</T>
|
||||
<T>pronouns.examples.header</T><T>quotation.colon</T>
|
||||
</h2>
|
||||
|
||||
<ul>
|
||||
<template v-for="example in examples">
|
||||
<li v-if="!example.comprehensive || comprehensive" class="my-1">
|
||||
<Example :example="example" :pronoun="randomPronounForExample(example)" link />
|
||||
</li>
|
||||
<template v-for="exampleCategory in exampleCategories">
|
||||
<ExampleCategory
|
||||
v-if="!exampleCategory.comprehensive || comprehensive"
|
||||
:example-category="exampleCategory"
|
||||
:pronouns-choice="pronounsChoice"
|
||||
link
|
||||
/>
|
||||
</template>
|
||||
</ul>
|
||||
</section>
|
||||
@ -67,6 +70,7 @@
|
||||
<script>
|
||||
import { examples, pronouns, pronounLibrary } from '../src/data.js';
|
||||
import { head } from '../src/helpers.ts';
|
||||
import { ExampleCategory } from '../src/classes.ts';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@ -82,11 +86,9 @@ export default {
|
||||
}
|
||||
|
||||
return {
|
||||
examples,
|
||||
exampleCategories: ExampleCategory.from(examples, this.$config),
|
||||
short,
|
||||
pronounGroups,
|
||||
|
||||
comprehensive: false,
|
||||
};
|
||||
},
|
||||
head() {
|
||||
@ -96,23 +98,34 @@ export default {
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
comprehensive: {
|
||||
get() {
|
||||
return Object.hasOwn(this.$route.query, this.$config.pronouns.comprehensive);
|
||||
},
|
||||
set(value) {
|
||||
if (value === this.comprehensive) {
|
||||
// prevent warning that $router.replace has no effect
|
||||
return;
|
||||
}
|
||||
const query = structuredClone(this.$route.query);
|
||||
if (value) {
|
||||
query[this.$config.pronouns.comprehensive] = null;
|
||||
} else {
|
||||
delete query[this.$config.pronouns.comprehensive];
|
||||
}
|
||||
this.$router.replace({ query });
|
||||
},
|
||||
},
|
||||
pronounsChoice() {
|
||||
if (!this.pronounGroups.length) {
|
||||
return pronouns;
|
||||
return Object.values(pronouns);
|
||||
}
|
||||
|
||||
let choice = {};
|
||||
for (const pronounGroup of this.pronounGroups) {
|
||||
choice = { ...choice, ...pronounGroup.groupPronouns };
|
||||
}
|
||||
return choice;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
randomPronounForExample(example) {
|
||||
const suitablePronouns = Object.values(this.pronounsChoice)
|
||||
.filter((pronoun) => example.requiredMorphemesPresent(pronoun));
|
||||
return suitablePronouns[suitablePronouns.length * Math.random() << 0];
|
||||
return Object.values(choice);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -55,17 +55,18 @@
|
||||
<section>
|
||||
<h2 class="h4">
|
||||
<Icon v="file-signature" />
|
||||
<T>pronouns.examples</T><T>quotation.colon</T>
|
||||
<T>pronouns.examples.header</T><T>quotation.colon</T>
|
||||
</h2>
|
||||
|
||||
<ul>
|
||||
<template v-for="example in examples">
|
||||
<li
|
||||
v-if="example.requiredMorphemesPresent(selectedPronoun, counter) && (!example.comprehensive || comprehensive)"
|
||||
class="my-1"
|
||||
>
|
||||
<Example :example="example" :pronoun="selectedPronoun" :counter="counter" pronunciation />
|
||||
</li>
|
||||
<template v-for="exampleCategory in exampleCategories">
|
||||
<ExampleCategory
|
||||
v-if="!exampleCategory.comprehensive || comprehensive"
|
||||
:example-category="exampleCategory"
|
||||
:pronouns-choice="[selectedPronoun]"
|
||||
:counter="counter"
|
||||
pronunciation
|
||||
/>
|
||||
</template>
|
||||
</ul>
|
||||
</section>
|
||||
@ -126,7 +127,7 @@ import { buildPronoun } from '../src/buildPronoun.ts';
|
||||
import { head } from '../src/helpers.ts';
|
||||
import GrammarTables from '../data/pronouns/GrammarTables.vue';
|
||||
import LinkedText from '../components/LinkedText.vue';
|
||||
import { SourceLibrary } from '../src/classes.ts';
|
||||
import { ExampleCategory, SourceLibrary } from '../src/classes.ts';
|
||||
|
||||
export default {
|
||||
components: { LinkedText, GrammarTables },
|
||||
@ -143,7 +144,7 @@ export default {
|
||||
: null;
|
||||
|
||||
return {
|
||||
examples,
|
||||
exampleCategories: ExampleCategory.from(examples, this.$config),
|
||||
pronouns,
|
||||
glue: ` ${this.$t('pronouns.or')} `,
|
||||
|
||||
@ -163,7 +164,7 @@ export default {
|
||||
? head({
|
||||
title: `${this.$t('pronouns.intro')}: ${this.selectedPronoun.name(this.glue)}`,
|
||||
description: [
|
||||
this.$t('pronouns.examples', {}, false),
|
||||
this.$t('pronouns.examples.header', {}, false),
|
||||
this.$t('pronouns.grammarTable', {}, false),
|
||||
this.$t('sources.headerLong', {}, false),
|
||||
].filter((x) => !!x).join(', '),
|
||||
|
@ -85,55 +85,51 @@
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
<T>pronouns.examples</T><T>quotation.colon</T>
|
||||
<T>pronouns.examples.header</T><T>quotation.colon</T>
|
||||
</p>
|
||||
<template v-for="isHonorific in [false, true]">
|
||||
<template v-if="examples.filter(e => e.isHonorific === isHonorific).length">
|
||||
<ul>
|
||||
<template v-for="example in examples">
|
||||
<li v-if="example.isHonorific === isHonorific && !example.comprehensive">
|
||||
<span v-for="part in clearExampleParts(example.parts(selectedPronoun))">
|
||||
<input
|
||||
v-if="part.variable"
|
||||
v-model="selectedPronoun.morphemes[part.str]"
|
||||
:class="['form-control form-input p-0', { active: selectedMorpheme === part.str }]"
|
||||
:size="selectedPronoun.morphemes[part.str]?.length ?? 0"
|
||||
maxlength="24"
|
||||
@focus="selectedMorpheme = part.str"
|
||||
@blur="selectedMorpheme = ''"
|
||||
>
|
||||
<span v-else><Spelling :text="part.str" /></span>
|
||||
</span>
|
||||
</li>
|
||||
<template v-for="{ examples, isHonorific } in examplesByHonorific">
|
||||
<ul>
|
||||
<li v-for="example in examples">
|
||||
<template v-for="part in clearExampleParts(example.parts(selectedPronoun))">
|
||||
<input
|
||||
v-if="part.variable"
|
||||
v-model="selectedPronoun.morphemes[part.str]"
|
||||
:class="['form-control form-input p-0', { active: selectedMorpheme === part.str }]"
|
||||
:size="selectedPronoun.morphemes[part.str]?.length ?? 0"
|
||||
maxlength="24"
|
||||
@focus="selectedMorpheme = part.str"
|
||||
@blur="selectedMorpheme = ''"
|
||||
>
|
||||
<span v-else><Spelling :text="part.str" /></span>
|
||||
</template>
|
||||
</ul>
|
||||
<div v-if="$config.pronouns.plurals" class="my-3">
|
||||
<div v-if="isHonorific" class="custom-control custom-switch">
|
||||
<input
|
||||
id="pluralHonorific"
|
||||
v-model="selectedPronoun.pluralHonorific[0]"
|
||||
type="checkbox"
|
||||
class="custom-control-input"
|
||||
>
|
||||
<label class="custom-control-label" for="pluralHonorific">
|
||||
<T>pronouns.plural</T>
|
||||
<Icon v="level-up" />
|
||||
</label>
|
||||
</div>
|
||||
<div v-else class="custom-control custom-switch">
|
||||
<input
|
||||
id="plural"
|
||||
v-model="selectedPronoun.plural[0]"
|
||||
type="checkbox"
|
||||
class="custom-control-input"
|
||||
>
|
||||
<label class="custom-control-label" for="plural">
|
||||
<T>pronouns.plural</T>
|
||||
<Icon v="level-up" />
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="$config.pronouns.plurals" class="my-3">
|
||||
<div v-if="isHonorific" class="custom-control custom-switch">
|
||||
<input
|
||||
id="pluralHonorific"
|
||||
v-model="selectedPronoun.pluralHonorific[0]"
|
||||
type="checkbox"
|
||||
class="custom-control-input"
|
||||
>
|
||||
<label class="custom-control-label" for="pluralHonorific">
|
||||
<T>pronouns.plural</T>
|
||||
<Icon v="level-up" />
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="custom-control custom-switch">
|
||||
<input
|
||||
id="plural"
|
||||
v-model="selectedPronoun.plural[0]"
|
||||
type="checkbox"
|
||||
class="custom-control-input"
|
||||
>
|
||||
<label class="custom-control-label" for="plural">
|
||||
<T>pronouns.plural</T>
|
||||
<Icon v="level-up" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<p class="small">
|
||||
<T icon="info-circle">home.generator.alt</T>
|
||||
@ -271,15 +267,15 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { examples, pronouns, pronounLibrary } from '../src/data.js';
|
||||
import { ExamplePart, Pronoun } from '../src/classes.ts';
|
||||
import { ExampleCategory, ExamplePart, Pronoun } from '../src/classes.ts';
|
||||
import Compressor from '../src/compressor.js';
|
||||
import MORPHEMES from '../data/pronouns/morphemes.js';
|
||||
import { mapState } from 'vuex';
|
||||
import Suggested from '../data/pronouns/Suggested.vue';
|
||||
import type { Example, PronounLibrary } from '../src/classes.ts';
|
||||
import type { PronounLibrary, Example } from '../src/classes.ts';
|
||||
|
||||
interface PronounsData {
|
||||
examples: Example[];
|
||||
examplesByHonorific: { examples: Example[], isHonorific: boolean }[];
|
||||
pronouns: Record<string, Pronoun>;
|
||||
pronounLibrary: PronounLibrary;
|
||||
selectedPronoun: Pronoun;
|
||||
@ -297,8 +293,18 @@ export default Vue.extend({
|
||||
if (!this.$config.pronouns.enabled) {
|
||||
throw null;
|
||||
}
|
||||
|
||||
const exampleCategories = ExampleCategory.from(examples, this.$config);
|
||||
const examplesByHonorific = [false, true].map((isHonorific) => {
|
||||
const examples = exampleCategories
|
||||
.filter((exampleCategory) => !exampleCategory.comprehensive)
|
||||
.map((exampleCategory) => exampleCategory.examples[0])
|
||||
.filter((example) => example.isHonorific === isHonorific);
|
||||
return { examples, isHonorific };
|
||||
}).filter(({ examples }) => examples.length > 0);
|
||||
|
||||
return {
|
||||
examples,
|
||||
examplesByHonorific,
|
||||
pronouns,
|
||||
pronounLibrary,
|
||||
|
||||
|
@ -4,7 +4,7 @@ import sha1 from 'sha1';
|
||||
import { ulid } from 'ulid';
|
||||
import Papa from 'papaparse';
|
||||
import { groupBy, handleErrorAsync } from '../../src/helpers.ts';
|
||||
import { intersection, difference } from '../../src/sets.js';
|
||||
import { intersection, difference } from '../../src/sets.ts';
|
||||
import { buildChart } from '../../src/stats.ts';
|
||||
import auditLog from '../audit.ts';
|
||||
|
||||
|
@ -19,13 +19,11 @@ export class Example {
|
||||
singularParts: ExamplePart[];
|
||||
pluralParts: ExamplePart[];
|
||||
isHonorific: boolean;
|
||||
comprehensive: boolean;
|
||||
|
||||
constructor(singularParts: ExamplePart[], pluralParts: ExamplePart[], isHonorific = false, comprehensive = false) {
|
||||
constructor(singularParts: ExamplePart[], pluralParts: ExamplePart[], isHonorific = false) {
|
||||
this.singularParts = singularParts;
|
||||
this.pluralParts = pluralParts;
|
||||
this.isHonorific = isHonorific;
|
||||
this.comprehensive = comprehensive;
|
||||
}
|
||||
|
||||
static parse(str: string): ExamplePart[] {
|
||||
@ -57,6 +55,12 @@ export class Example {
|
||||
return this[plural ? 'pluralParts' : 'singularParts'];
|
||||
}
|
||||
|
||||
hasMorpheme(morpheme: string): boolean {
|
||||
return this.singularParts.filter((part) => part.variable).some((part) => {
|
||||
return part.str.replace(/^'/, '') === morpheme;
|
||||
});
|
||||
}
|
||||
|
||||
requiredMorphemesPresent(pronoun: Pronoun, counter = 0): boolean {
|
||||
return this.parts(pronoun, counter).filter((part) => part.variable)
|
||||
.every((part) => pronoun.getMorpheme(part.str, counter) !== null);
|
||||
@ -116,6 +120,30 @@ export class Example {
|
||||
}
|
||||
}
|
||||
|
||||
export class ExampleCategory {
|
||||
name: string | undefined;
|
||||
examples: Example[];
|
||||
comprehensive: boolean;
|
||||
|
||||
constructor(name: string | undefined, examples: Example[], comprehensive: boolean = false) {
|
||||
this.name = name;
|
||||
this.examples = examples;
|
||||
this.comprehensive = comprehensive;
|
||||
}
|
||||
|
||||
static from(examples: Example[], config: Config): ExampleCategory[] {
|
||||
if (!config.pronouns.exampleCategories) {
|
||||
return examples.map((example) => new ExampleCategory(undefined, [example]));
|
||||
}
|
||||
return config.pronouns.exampleCategories.map((exampleCategory) => {
|
||||
const matchingExamples = examples.filter((example) => {
|
||||
return exampleCategory.morphemes.some((morpheme) => example.hasMorpheme(morpheme));
|
||||
});
|
||||
return new ExampleCategory(exampleCategory.name, matchingExamples, exampleCategory.comprehensive);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function clone<T extends object>(mainObject: T): T {
|
||||
const objectCopy = {} as T;
|
||||
for (const [key, value] of Object.entries(mainObject)) {
|
||||
|
@ -12,7 +12,6 @@ export const examples = buildList(function* () {
|
||||
Example.parse(e.singular),
|
||||
Example.parse(e.plural || e.singular),
|
||||
e.isHonorific,
|
||||
e.comprehensive || false,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -82,6 +82,10 @@ export function listMissingTranslations(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.pronouns.exampleCategories && keyMatches('pronouns.examples.shuffleNamed')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.pronouns.slashes && keyMatches('pronouns.slashes.')) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
|
||||
|
||||
export const isSuperset = (set, subset) => {
|
||||
export const isSuperset = <T>(set: Set<T>, subset: Set<T>): boolean => {
|
||||
for (const elem of subset) {
|
||||
if (!set.has(elem)) {
|
||||
return false;
|
||||
@ -9,7 +9,7 @@ export const isSuperset = (set, subset) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const union = (setA, setB) => {
|
||||
export const union = <T>(setA: Set<T>, setB: Set<T>): Set<T> => {
|
||||
const _union = new Set(setA);
|
||||
for (const elem of setB) {
|
||||
_union.add(elem);
|
||||
@ -17,8 +17,8 @@ export const union = (setA, setB) => {
|
||||
return _union;
|
||||
};
|
||||
|
||||
export const intersection = (setA, setB) => {
|
||||
const _intersection = new Set();
|
||||
export const intersection = <T>(setA: Set<T>, setB: Set<T>): Set<T> => {
|
||||
const _intersection: Set<T> = new Set();
|
||||
for (const elem of setB) {
|
||||
if (setA.has(elem)) {
|
||||
_intersection.add(elem);
|
||||
@ -27,7 +27,7 @@ export const intersection = (setA, setB) => {
|
||||
return _intersection;
|
||||
};
|
||||
|
||||
export const symmetricDifference = (setA, setB) => {
|
||||
export const symmetricDifference = <T>(setA: Set<T>, setB: Set<T>): Set<T> => {
|
||||
const _difference = new Set(setA);
|
||||
for (const elem of setB) {
|
||||
if (_difference.has(elem)) {
|
||||
@ -39,7 +39,7 @@ export const symmetricDifference = (setA, setB) => {
|
||||
return _difference;
|
||||
};
|
||||
|
||||
export const difference = (setA, setB) => {
|
||||
export const difference = <T>(setA: Set<T>, setB: Set<T>): Set<T> => {
|
||||
const _difference = new Set(setA);
|
||||
for (const elem of setB) {
|
||||
_difference.delete(elem);
|
@ -7,7 +7,7 @@ export interface RootState {
|
||||
user: User | null;
|
||||
preToken: string | null;
|
||||
spelling: string | null;
|
||||
highlightedMorpheme: string | null;
|
||||
highlightedMorphemes: Set<string>;
|
||||
darkMode: boolean;
|
||||
translationModeVisible: boolean;
|
||||
translationMode: boolean;
|
||||
@ -22,7 +22,7 @@ export const state: () => RootState = () => ({
|
||||
user: null,
|
||||
preToken: null,
|
||||
spelling: null,
|
||||
highlightedMorpheme: null,
|
||||
highlightedMorphemes: new Set(),
|
||||
darkMode: false,
|
||||
translationModeVisible: false,
|
||||
translationMode: false,
|
||||
@ -63,8 +63,8 @@ export const mutations: MutationTree<RootState> = {
|
||||
setSpelling(state, spelling) {
|
||||
state.spelling = spelling;
|
||||
},
|
||||
highlightMorpheme(state, morpheme) {
|
||||
state.highlightedMorpheme = morpheme;
|
||||
highlightMorphemes(state, morphemes: Set<string>) {
|
||||
state.highlightedMorphemes = morphemes;
|
||||
},
|
||||
setDarkMode(state, isDark) {
|
||||
state.darkMode = isDark;
|
||||
|
@ -21,21 +21,41 @@ const translator = new Translator(translations, translations, []);
|
||||
|
||||
const { default: pronouns, generated: generatedPronouns } = await import('./fixtures/pronouns.ts');
|
||||
|
||||
describe('required morphemes for an example', () => {
|
||||
const exampleParts = [
|
||||
new ExamplePart(false, 'This house is '),
|
||||
new ExamplePart(true, 'possessive_pronoun'),
|
||||
];
|
||||
const example = new Example(exampleParts, exampleParts);
|
||||
const inlineMorphemeExampleParts = [
|
||||
new ExamplePart(false, 'This house is '),
|
||||
new ExamplePart(true, 'possessive_pronoun'),
|
||||
];
|
||||
const inlineMorphemeExample = new Example(inlineMorphemeExampleParts, inlineMorphemeExampleParts);
|
||||
|
||||
const capitalizedMorphemeExampleParts = [
|
||||
new ExamplePart(true, '\'pronoun_subject'),
|
||||
new ExamplePart(false, ' is very nice.'),
|
||||
];
|
||||
const capitalizedMorphemeExample = new Example(capitalizedMorphemeExampleParts, capitalizedMorphemeExampleParts);
|
||||
|
||||
describe('morphemes of examples', () => {
|
||||
test('are contained when present as variable', () => {
|
||||
expect(inlineMorphemeExample.hasMorpheme('possessive_pronoun')).toBe(true);
|
||||
});
|
||||
test('are contained when present as capitalized variable', () => {
|
||||
expect(capitalizedMorphemeExample.hasMorpheme('pronoun_subject')).toBe(true);
|
||||
});
|
||||
test('are not contained when missing as variable', () => {
|
||||
expect(inlineMorphemeExample.hasMorpheme('pronoun_subject')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('required morphemes for an example', () => {
|
||||
test('are present if all morphemes are present', () => {
|
||||
expect(example.requiredMorphemesPresent(pronouns.they)).toBe(true);
|
||||
expect(inlineMorphemeExample.requiredMorphemesPresent(pronouns.they)).toBe(true);
|
||||
});
|
||||
test('are present even if one morpheme is empty', () => {
|
||||
expect(example.requiredMorphemesPresent(generatedPronouns.aerWithEmptyPossessivePronoun)).toBe(true);
|
||||
expect(inlineMorphemeExample.requiredMorphemesPresent(generatedPronouns.aerWithEmptyPossessivePronoun))
|
||||
.toBe(true);
|
||||
});
|
||||
test('are missing if one morpheme is null', () => {
|
||||
expect(example.requiredMorphemesPresent(generatedPronouns.aerWithUnsetPossessivePronoun)).toBe(false);
|
||||
expect(inlineMorphemeExample.requiredMorphemesPresent(generatedPronouns.aerWithUnsetPossessivePronoun))
|
||||
.toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -2,11 +2,39 @@ import { describe, expect, test } from '@jest/globals';
|
||||
|
||||
import allLocales from '../../locale/locales.ts';
|
||||
import { loadTsv } from '../../src/tsv.js';
|
||||
import { Example } from '../../src/classes.ts';
|
||||
import type { ExpectationResult } from 'expect';
|
||||
|
||||
const __dirname = new URL('.', import.meta.url).pathname;
|
||||
|
||||
function toHaveValidMorphemes(actual: string, morphemes: string[]): ExpectationResult {
|
||||
const containedMorphemes = Example.parse(actual).filter((part) => part.variable)
|
||||
.map((part) => part.str.replace(/^'/, ''));
|
||||
const unknownMorphemes = containedMorphemes.filter((morpheme) => !morphemes.includes(morpheme));
|
||||
if (unknownMorphemes.length > 0) {
|
||||
return {
|
||||
message: () => `expected example '${actual}' to have valid morphemes,` +
|
||||
` but these are unknown:\n${unknownMorphemes.join(', ')}`,
|
||||
pass: false,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
message: () => 'expected example to have invalid morphemes',
|
||||
pass: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'expect' {
|
||||
interface Matchers<R> {
|
||||
toHaveValidMorphemes(morphemes: string[]): R;
|
||||
}
|
||||
}
|
||||
|
||||
expect.extend({ toHaveValidMorphemes });
|
||||
|
||||
describe.each(allLocales)('data files of $code', ({ code }) => {
|
||||
test('pronouns.tsv match schema', async () => {
|
||||
test('pronouns/pronouns.tsv match schema', async () => {
|
||||
const { default: MORPHEMES } = await import(`../../locale/${code}/pronouns/morphemes.js`);
|
||||
const pronouns = loadTsv(`${__dirname}/../../locale/${code}/pronouns/pronouns.tsv`);
|
||||
if (pronouns.length === 0) {
|
||||
@ -26,4 +54,14 @@ describe.each(allLocales)('data files of $code', ({ code }) => {
|
||||
expect(actual).toEqual(expect.arrayContaining(required));
|
||||
expect([...required, ...optional]).toEqual(expect.arrayContaining(actual));
|
||||
});
|
||||
test('pronouns/examples.tsv contain valid morphemes', async () => {
|
||||
const { default: MORPHEMES } = await import(`../../locale/${code}/pronouns/morphemes.js`);
|
||||
const examples = loadTsv(`${__dirname}/../../locale/${code}/pronouns/examples.tsv`);
|
||||
for (const example of examples) {
|
||||
expect(example.singular).toHaveValidMorphemes(MORPHEMES);
|
||||
if (example.plural) {
|
||||
expect(example.plural).toHaveValidMorphemes(MORPHEMES);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user