mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-24 05:05:20 -04:00
Merge branch 'pronouns-consistency-and-picker' into 'main'
(pronouns) consistent styling across pages and a picker for the editor See merge request PronounsPage/PronounsPage!531
This commit is contained in:
commit
65fe7d7dff
@ -36,7 +36,7 @@ import type { Source } from '../src/classes.ts';
|
||||
export default defineComponent({
|
||||
props: {
|
||||
pronoun: { },
|
||||
sources: { required: true, type: Object as PropType<Record<string, Source[]>> },
|
||||
sources: { required: true, type: Object as PropType<Record<string, Source[] | undefined>> },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -47,7 +47,9 @@ export default defineComponent({
|
||||
},
|
||||
computed: {
|
||||
visibleSources(): Record<string, Source[]> {
|
||||
return Object.fromEntries(Object.entries(this.sources).filter(([_, sources]) => sources));
|
||||
return Object.fromEntries(
|
||||
Object.entries(this.sources).filter(([_, sources]) => sources),
|
||||
) as Record<string, Source[]>;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -1,32 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import type { PronounGroup, Pronoun } from '~/src/classes.ts';
|
||||
|
||||
const props = defineProps<{
|
||||
pronounGroup: { group: PronounGroup, groupPronouns: Record<string, Pronoun> | Pronoun[] };
|
||||
defineProps<{
|
||||
pronounGroup?: PronounGroup;
|
||||
pronouns: Record<string, Pronoun> | Pronoun[] | string[];
|
||||
}>();
|
||||
|
||||
const pronouns = computed(() => {
|
||||
if (Array.isArray(props.pronounGroup.groupPronouns)) {
|
||||
return props.pronounGroup.groupPronouns;
|
||||
}
|
||||
return Object.values(props.pronounGroup.groupPronouns);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section v-if="pronounGroup && pronounGroup.group.name">
|
||||
<section>
|
||||
<ul class="list-group mt-4">
|
||||
<li class="list-group-item">
|
||||
<p class="h5">
|
||||
<Spelling :text="pronounGroup.group.name" />
|
||||
<p v-if="pronounGroup?.name" class="h5">
|
||||
<Spelling :text="pronounGroup.name" />
|
||||
</p>
|
||||
<p v-if="pronounGroup.group.description" class="small my-1">
|
||||
<p v-if="pronounGroup?.description" class="small my-1">
|
||||
<Icon v="info-circle" />
|
||||
<LinkedText :text="pronounGroup.group.description" />
|
||||
<LinkedText :text="pronounGroup.description" />
|
||||
</p>
|
||||
<ul class="list-unstyled">
|
||||
<SimplePronounList :pronouns="pronouns" />
|
||||
</ul>
|
||||
<SimplePronounList :pronouns="Array.isArray(pronouns) ? pronouns : Object.values(pronouns)" />
|
||||
</li>
|
||||
<nuxt-link :to="{ name: 'pronouns' }" class="list-group-item list-group-item-action text-center">
|
||||
<Icon v="ellipsis-h-alt" />
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { buildPronoun } from '~/src/buildPronoun.ts';
|
||||
import { buildPronounUsage } from '~/src/buildPronoun.ts';
|
||||
import type { Pronoun } from '~/src/classes.ts';
|
||||
import { pronouns as pronounsData } from '~/src/data.ts';
|
||||
import { pronounLibrary } from '~/src/data.ts';
|
||||
|
||||
const props = defineProps<{
|
||||
pronouns: Pronoun[] | string[];
|
||||
@ -10,40 +10,43 @@ const props = defineProps<{
|
||||
const { $translator: translator } = useNuxtApp();
|
||||
const config = useConfig();
|
||||
|
||||
const glue = ` ${translator.translate('pronouns.or')} `;
|
||||
|
||||
const visiblePronouns = computed(() => {
|
||||
return props.pronouns
|
||||
.map((pronoun): Pronoun | undefined => {
|
||||
.map((pronoun) => {
|
||||
if (typeof pronoun === 'string') {
|
||||
const parsed = buildPronoun(pronounsData, pronoun, config, translator);
|
||||
if (parsed) {
|
||||
parsed.canonicalName = pronoun;
|
||||
parsed.description = '';
|
||||
return parsed;
|
||||
const usage = buildPronounUsage(pronounLibrary, pronoun, config, translator);
|
||||
if (usage) {
|
||||
return {
|
||||
path: pronoun,
|
||||
short: usage.short.options.join(usage.short.glue),
|
||||
};
|
||||
}
|
||||
} else if (!pronoun.hidden) {
|
||||
return pronoun;
|
||||
return {
|
||||
path: pronoun.canonicalName,
|
||||
short: pronoun.name(glue),
|
||||
description: pronoun.description,
|
||||
normative: pronoun.normative,
|
||||
smallForm: pronoun.smallForm ? pronoun.morphemes[pronoun.smallForm] : undefined,
|
||||
};
|
||||
}
|
||||
})
|
||||
.filter((entry) => entry !== undefined);
|
||||
});
|
||||
|
||||
const glue = ` ${translator.translate('pronouns.or')} `;
|
||||
|
||||
const addSlash = (link: string): string => {
|
||||
return link + (['*', '\''].includes(link.substring(link.length - 1)) ? '/' : '');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul>
|
||||
<li v-for="pronoun in visiblePronouns" :key="pronoun.canonicalName">
|
||||
<nuxt-link :to="addSlash(`${config.pronouns.prefix || ''}/${pronoun.canonicalName}`)">
|
||||
<strong><Spelling :text="pronoun.name(glue)" /></strong><small v-if="pronoun.smallForm">/<Spelling :text="pronoun.morphemes[pronoun.smallForm] ?? undefined" /></small>
|
||||
<li v-for="pronoun in visiblePronouns" :key="pronoun.path">
|
||||
<PronounsIndexLink :path="pronoun.path">
|
||||
<strong><Spelling :text="pronoun.short" /></strong><small v-if="pronoun.smallForm">/<Spelling :text="pronoun.smallForm" /></small>
|
||||
<template v-if="pronoun.description">
|
||||
–
|
||||
<small><Spelling :text="pronoun.description as string" /></small>
|
||||
</template>
|
||||
</nuxt-link>
|
||||
</PronounsIndexLink>
|
||||
<NormativeBadge v-if="pronoun.normative" />
|
||||
</li>
|
||||
</ul>
|
||||
|
201
components/pronouns/PronounsCustomGenerator.vue
Normal file
201
components/pronouns/PronounsCustomGenerator.vue
Normal file
@ -0,0 +1,201 @@
|
||||
<script setup lang="ts">
|
||||
import { ExampleCategory, ExamplePart, Pronoun } from '~/src/classes.ts';
|
||||
import Compressor from '~/src/compressor.ts';
|
||||
import { examples, pronounLibrary, pronouns } from '~/src/data.ts';
|
||||
import MORPHEMES from '~/data/pronouns/morphemes.ts';
|
||||
|
||||
const config = useConfig();
|
||||
if (!config.pronouns.enabled || !config.pronouns.generator.enabled) {
|
||||
throw new Error('config.pronouns.generator is disabled');
|
||||
}
|
||||
|
||||
const { $translator: translator } = useNuxtApp();
|
||||
|
||||
const exampleCategories = ExampleCategory.from(examples, 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);
|
||||
|
||||
const selectedPronoun = ref(pronouns[config.pronouns.default].clone(true));
|
||||
const selectedMorpheme = ref('');
|
||||
|
||||
const glue = ` ${translator.translate('pronouns.or')} `;
|
||||
|
||||
const clearExampleParts = (parts: ExamplePart[]): ExamplePart[] => {
|
||||
return parts.map((p) => new ExamplePart(p.variable, p.str.replace(/^'/, '')));
|
||||
};
|
||||
|
||||
const deduplicatePronounGroup = (pronounGroup: Pronoun[]): Pronoun[] => {
|
||||
const dict: Record<string, Pronoun> = {};
|
||||
for (const pronoun of pronounGroup) {
|
||||
if (Object.hasOwn(dict, pronoun.name(glue))) {
|
||||
continue;
|
||||
}
|
||||
dict[pronoun.name(glue)] = pronoun;
|
||||
}
|
||||
return Object.values(dict);
|
||||
};
|
||||
|
||||
const usedBase = computed((): string | null => {
|
||||
const name = selectedPronoun.value.name(glue);
|
||||
for (const key in pronouns) {
|
||||
if (Object.hasOwn(pronouns, key)) {
|
||||
if (key === name) {
|
||||
return key;
|
||||
}
|
||||
for (const alias of pronouns[key].aliases) {
|
||||
if (alias === name) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
const usedBaseEquals = computed((): boolean => {
|
||||
return !!usedBase.value && selectedPronoun.value.equals(pronouns[usedBase.value], true);
|
||||
});
|
||||
|
||||
const longPath = computed((): string => {
|
||||
const base = pronouns[selectedPronoun.value.morphemes[MORPHEMES[0]]!];
|
||||
|
||||
return base
|
||||
? Compressor.compress(
|
||||
selectedPronoun.value.toArray().map((x) => x.split('|')[0]),
|
||||
base.toArray().map((x) => x.split('|')[0]),
|
||||
).join(',')
|
||||
: selectedPronoun.value.toString();
|
||||
});
|
||||
const path = computed((): string | null => {
|
||||
if (!selectedPronoun.value.pronoun()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const slashes = selectedPronoun.value.toStringSlashes(translator);
|
||||
|
||||
if (usedBaseEquals.value) {
|
||||
return usedBase.value;
|
||||
} else if (slashes) {
|
||||
return slashes;
|
||||
} else {
|
||||
return longPath.value;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PronounsGenerator
|
||||
v-if="config.pronouns.enabled && config.pronouns.generator.enabled"
|
||||
type="generator"
|
||||
:path="path"
|
||||
:initially-open="config.pronouns.generator.autoOpen ?? false"
|
||||
:disclaimer="!usedBaseEquals"
|
||||
>
|
||||
<div class="card-title border-bottom pb-3">
|
||||
<p><strong><T>home.generator.base</T><T>quotation.colon</T></strong></p>
|
||||
<ul class="list-unstyled">
|
||||
<template v-for="[group, groupPronouns] in pronounLibrary.split()" :key="group.key">
|
||||
<li v-if="!group.hidden">
|
||||
<ul class="list-inline">
|
||||
<li class="list-inline-item">
|
||||
<Spelling :text="group.name" />
|
||||
</li>
|
||||
<template
|
||||
v-for="pronoun in deduplicatePronounGroup(groupPronouns)"
|
||||
:key="pronoun.canonicalName"
|
||||
>
|
||||
<li v-if="!pronoun.hidden" class="list-inline-item">
|
||||
<button
|
||||
:class="['btn', pronoun.name(glue) === selectedPronoun.name(glue) ? 'btn-primary' : 'btn-outline-primary', 'btn-sm', 'my-1']"
|
||||
@click="selectedPronoun = pronoun.clone(true)"
|
||||
>
|
||||
<Spelling :text="pronoun.name(glue)" />
|
||||
</button>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="alert alert-primary">
|
||||
<p class="h3 mb-0 text-center">
|
||||
<Spelling escape :text="selectedPronoun.name(glue)" />
|
||||
<template v-if="config.pronouns.generator.description ?? true">
|
||||
<br>
|
||||
<input
|
||||
v-model="selectedPronoun.description"
|
||||
class="form-control form-input p-0 form-control-sm"
|
||||
:size="selectedPronoun.description.length ? selectedPronoun.description.length + 3 : 16"
|
||||
:maxlength="Pronoun.DESCRIPTION_MAXLENGTH"
|
||||
:placeholder="$t('profile.description')"
|
||||
>
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
<T>pronouns.examples.header</T><T>quotation.colon</T>
|
||||
</p>
|
||||
<template
|
||||
v-for="{ examples: examplesOfHonorific, isHonorific } in examplesByHonorific"
|
||||
:key="isHonorific"
|
||||
>
|
||||
<ul>
|
||||
<li v-for="(example, i) in examplesOfHonorific" :key="i">
|
||||
<template v-for="(part, j) in clearExampleParts(example.parts(selectedPronoun))" :key="j">
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
</p>
|
||||
<!-- TODO #136
|
||||
<p class="small" v-if="config.pronunciation.enabled && $te('home.generator.pronunciation')">
|
||||
<Icon v="info-circle"/>
|
||||
<T>home.generator.pronunciation</T>
|
||||
</p>
|
||||
-->
|
||||
</PronounsGenerator>
|
||||
</template>
|
25
components/pronouns/PronounsEmojiGenerator.vue
Normal file
25
components/pronouns/PronounsEmojiGenerator.vue
Normal file
@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import { isEmoji } from '~/src/helpers.ts';
|
||||
|
||||
const base = ref('');
|
||||
|
||||
const path = computed((): string | null => {
|
||||
if (!isEmoji(base.value)) {
|
||||
return null;
|
||||
}
|
||||
return base.value;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PronounsGenerator type="emoji" :path="path" disclaimer>
|
||||
<div class="form-group">
|
||||
<label for="emojiPronounsBase"><T>pronouns.emoji.base</T></label>
|
||||
<input
|
||||
id="emojiPronounsBase"
|
||||
v-model="base"
|
||||
class="form-control"
|
||||
>
|
||||
</div>
|
||||
</PronounsGenerator>
|
||||
</template>
|
63
components/pronouns/PronounsGenerator.vue
Normal file
63
components/pronouns/PronounsGenerator.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<script setup lang="ts">
|
||||
import { addSlash } from '~/src/helpers.ts';
|
||||
import { addPronounInjectionKey } from '~/src/injectionKeys.ts';
|
||||
|
||||
const props = defineProps<{
|
||||
type: 'generator' | 'alt' | 'null' | 'emoji';
|
||||
path: string | null;
|
||||
disclaimer?: boolean;
|
||||
initiallyOpen?: boolean;
|
||||
}>();
|
||||
|
||||
const addPronoun = inject(addPronounInjectionKey, undefined);
|
||||
|
||||
const runtimeConfig = useRuntimeConfig();
|
||||
const config = useConfig();
|
||||
|
||||
const open = ref(props.initiallyOpen ?? false);
|
||||
|
||||
const link = computed((): string | null => {
|
||||
if (!props.path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return addSlash(`${runtimeConfig.public.baseUrl + (config.pronouns.prefix || '')}/${props.path}`);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button v-if="!open" type="button" class="btn btn-outline-primary w-100" @click="open = true">
|
||||
<Icon v="sliders-h-square" />
|
||||
<T v-if="type === 'generator'">home.generator.button</T>
|
||||
<T v-else>pronouns.{{ type }}.button</T>
|
||||
</button>
|
||||
<div v-else class="card">
|
||||
<div class="card-header">
|
||||
<Icon v="sliders-h-square" />
|
||||
<T v-if="type === 'generator'">home.generator.header2</T>
|
||||
<template v-else>
|
||||
<T>pronouns.{{ type }}.button</T><T>quotation.colon</T>
|
||||
</template>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div v-if="path" class="card-footer">
|
||||
<LinkInput v-if="addPronoun === undefined" :link="link" />
|
||||
<div v-else class="input-group my-2">
|
||||
<input class="form-control" readonly :value="path">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-success"
|
||||
@click="addPronoun(path)"
|
||||
>
|
||||
<Icon v="plus-circle" />
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="disclaimer && (config.pronouns.generator?.disclaimer ?? true)" class="alert alert-warning">
|
||||
<Icon v="exclamation-triangle" />
|
||||
<T>pronouns.generated</T>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
115
components/pronouns/PronounsIndex.vue
Normal file
115
components/pronouns/PronounsIndex.vue
Normal file
@ -0,0 +1,115 @@
|
||||
<script setup lang="ts">
|
||||
import { buildAnyPronounsList, headerForVariant } from '~/src/buildPronoun.ts';
|
||||
import { pronounLibrary } from '~/src/data.ts';
|
||||
|
||||
defineProps<{
|
||||
examples?: boolean;
|
||||
}>();
|
||||
|
||||
const config = useConfig();
|
||||
|
||||
const anyPronouns = buildAnyPronounsList(config, pronounLibrary);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul v-if="config.pronouns.enabled" class="list-group mt-4">
|
||||
<template v-for="[group, groupPronouns] in pronounLibrary.split()" :key="group.key">
|
||||
<li v-if="!group.hidden" class="list-group-item">
|
||||
<p class="h5">
|
||||
<Spelling :text="group.name" />
|
||||
</p>
|
||||
<p v-if="group.description" class="small my-1">
|
||||
<Icon v="info-circle" />
|
||||
<LinkedText :text="group.description" />
|
||||
</p>
|
||||
<SimplePronounList :pronouns="groupPronouns" />
|
||||
</li>
|
||||
</template>
|
||||
<li v-if="config.pronouns.generator.enabled" id="generator" class="list-group-item">
|
||||
<p class="h5">
|
||||
<T>home.generator.header</T>
|
||||
</p>
|
||||
<p class="small my-1">
|
||||
<Icon v="info-circle" />
|
||||
<T>home.generator.description</T>
|
||||
</p>
|
||||
<PronounsCustomGenerator />
|
||||
</li>
|
||||
<li id="multiple" class="list-group-item">
|
||||
<p class="h5">
|
||||
<Spelling :text="config.pronouns.multiple.name" />
|
||||
</p>
|
||||
<p v-if="config.pronouns.multiple.description" class="small my-1">
|
||||
<Icon v="info-circle" />
|
||||
<LinkedText :text="config.pronouns.multiple.description" />
|
||||
</p>
|
||||
<SimplePronounList v-if="examples" :pronouns="config.pronouns.multiple.examples" class="mb-3" />
|
||||
<PronounsMultipleGenerator />
|
||||
</li>
|
||||
<li v-if="config.pronouns.null !== false" id="nameself" class="list-group-item">
|
||||
<p class="h5">
|
||||
<template v-for="(variant, i) in config.pronouns.null.routes" :key="variant">
|
||||
{{ i === 0 ? '' : ' / ' }}
|
||||
<PronounsIndexLink :path="variant">
|
||||
<Spelling :text="headerForVariant('null', variant, $translator)" />
|
||||
</PronounsIndexLink>
|
||||
</template>
|
||||
<NormativeBadge />
|
||||
</p>
|
||||
<p class="small my-1">
|
||||
<Icon v="info-circle" />
|
||||
<T>pronouns.null.description</T>
|
||||
</p>
|
||||
<SimplePronounList v-if="examples" :pronouns="config.pronouns.null.examples ?? []" class="mb-3" />
|
||||
<PronounsNullGenerator />
|
||||
</li>
|
||||
<li v-if="config.pronouns.emoji !== false" class="list-group-item">
|
||||
<p class="h5">
|
||||
<Spelling :text="config.pronouns.emoji.description" />
|
||||
</p>
|
||||
<div v-if="config.pronouns.emoji.history" class="small my-1">
|
||||
<Icon v="info-circle" />
|
||||
<LinkedText :text="config.pronouns.emoji.history" />
|
||||
</div>
|
||||
<SimplePronounList v-if="examples" :pronouns="config.pronouns.emoji.examples" class="mb-3" />
|
||||
<PronounsEmojiGenerator />
|
||||
</li>
|
||||
<li v-if="config.pronouns.mirror" id="mirror" class="list-group-item">
|
||||
<p class="h5">
|
||||
<PronounsIndexLink :path="config.pronouns.mirror.route">
|
||||
<LinkedText :text="config.pronouns.mirror.name" />
|
||||
</PronounsIndexLink>
|
||||
</p>
|
||||
<p v-if="config.pronouns.mirror.description" class="small my-1">
|
||||
<Icon v="info-circle" />
|
||||
<LinkedText :text="config.pronouns.mirror.description" />
|
||||
</p>
|
||||
</li>
|
||||
<li v-if="config.pronouns.any" class="list-group-item">
|
||||
<p class="h5">
|
||||
<PronounsIndexLink :path="config.pronouns.any">
|
||||
<T>pronouns.any.header</T>
|
||||
</PronounsIndexLink>
|
||||
</p>
|
||||
<p class="small my-1">
|
||||
<Icon v="info-circle" />
|
||||
<T>pronouns.any.description</T>
|
||||
</p>
|
||||
<SimplePronounList :pronouns="anyPronouns" />
|
||||
</li>
|
||||
<li v-if="config.pronouns.ask" class="list-group-item">
|
||||
<p class="h5">
|
||||
<template v-for="(variant, i) in config.pronouns.ask.routes" :key="variant">
|
||||
{{ i === 0 ? '' : ' / ' }}
|
||||
<PronounsIndexLink :path="variant">
|
||||
<Spelling :text="headerForVariant('ask', variant, $translator)" />
|
||||
</PronounsIndexLink>
|
||||
</template>
|
||||
</p>
|
||||
<p class="small my-1">
|
||||
<Icon v="info-circle" />
|
||||
<T>pronouns.ask.description</T>
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
27
components/pronouns/PronounsIndexLink.vue
Normal file
27
components/pronouns/PronounsIndexLink.vue
Normal file
@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { addSlash } from '~/src/helpers.ts';
|
||||
import { addPronounInjectionKey } from '~/src/injectionKeys.ts';
|
||||
|
||||
defineProps<{
|
||||
path: string;
|
||||
}>();
|
||||
|
||||
const addPronoun = inject(addPronounInjectionKey, undefined);
|
||||
|
||||
const config = useConfig();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nuxt-link
|
||||
v-if="addPronoun === undefined"
|
||||
:to="addSlash(`${config.pronouns.prefix || ''}/${encodeURIComponent(path)}`)"
|
||||
>
|
||||
<slot></slot>
|
||||
</nuxt-link>
|
||||
<template v-else>
|
||||
<span class="text-primary"><slot></slot></span>
|
||||
<button type="button" class="btn btn-sm p-0 text-success" @click="addPronoun(path)">
|
||||
<Icon v="plus-circle" hover />
|
||||
</button>
|
||||
</template>
|
||||
</template>
|
44
components/pronouns/PronounsMultipleGenerator.vue
Normal file
44
components/pronouns/PronounsMultipleGenerator.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import { pronouns } from '~/src/data.ts';
|
||||
|
||||
const config = useConfig();
|
||||
|
||||
const multiple = ref(config.pronouns.multiple ? config.pronouns.multiple.examples[0].split('&') : []);
|
||||
|
||||
const path = computed((): string | null => {
|
||||
if (!multiple.value.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return multiple.value.join('&');
|
||||
});
|
||||
|
||||
const togglePronoun = (name: string): void => {
|
||||
const index = multiple.value.indexOf(name);
|
||||
if (index > -1) {
|
||||
multiple.value.splice(index, 1);
|
||||
} else {
|
||||
multiple.value.push(name);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PronounsGenerator type="alt" :path="path">
|
||||
<div class="card-title">
|
||||
<ul class="list-inline d-inline mb-0">
|
||||
<template v-for="(pronoun, pronounName) in pronouns" :key="pronoun.canonicalName">
|
||||
<li v-if="!pronoun.hidden" class="list-inline-item">
|
||||
<button
|
||||
type="button"
|
||||
:class="['btn', multiple.includes(pronounName) ? 'btn-primary' : 'btn-outline-primary', 'btn-sm', 'my-1']"
|
||||
@click="togglePronoun(pronounName)"
|
||||
>
|
||||
<Spelling :text="pronoun.name('')" />
|
||||
</button>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</PronounsGenerator>
|
||||
</template>
|
33
components/pronouns/PronounsNullGenerator.vue
Normal file
33
components/pronouns/PronounsNullGenerator.vue
Normal file
@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { NULL_PRONOUNS_MAXLENGTH } from '~/src/buildPronoun.ts';
|
||||
|
||||
const config = useConfig();
|
||||
|
||||
const base = ref('');
|
||||
|
||||
const path = computed((): string | null => {
|
||||
if (!base.value) {
|
||||
return null;
|
||||
}
|
||||
return `:${base.value}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PronounsGenerator
|
||||
v-if="config.pronouns.enabled && config.pronouns.null && config.pronouns.null.morphemes"
|
||||
type="null"
|
||||
:path="path"
|
||||
disclaimer
|
||||
>
|
||||
<div class="form-group">
|
||||
<label for="nullPronounsBase"><T>pronouns.null.base</T></label>
|
||||
<input
|
||||
id="nullPronounsBase"
|
||||
v-model="base"
|
||||
class="form-control"
|
||||
:maxlength="NULL_PRONOUNS_MAXLENGTH"
|
||||
>
|
||||
</div>
|
||||
</PronounsGenerator>
|
||||
</template>
|
24
composables/useComprehensive.ts
Normal file
24
composables/useComprehensive.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export default () => {
|
||||
const config = useConfig();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const comprehensive = computed({
|
||||
get() {
|
||||
return config.pronouns.comprehensive ? Object.hasOwn(route.query, config.pronouns.comprehensive) : false;
|
||||
},
|
||||
set(value) {
|
||||
if (value === comprehensive.value || !config.pronouns.comprehensive) {
|
||||
// prevent warning that $router.replace has no effect
|
||||
return;
|
||||
}
|
||||
const query = structuredClone(route.query);
|
||||
if (value) {
|
||||
query[config.pronouns.comprehensive] = null;
|
||||
} else {
|
||||
delete query[config.pronouns.comprehensive];
|
||||
}
|
||||
router.replace({ query });
|
||||
},
|
||||
});
|
||||
return comprehensive;
|
||||
};
|
@ -21,14 +21,7 @@ pronouns:
|
||||
Some like when others alternate between those forms when talking about them.
|
||||
examples: ['he&she', 'he&they', 'she&they']
|
||||
null:
|
||||
description: >
|
||||
{/avoiding=Avoiding gendered forms} / {/no-pronouns=no pronouns} / {/null=null pronouns} /
|
||||
{/pronounless=pronounless} / {/nullpronominal=nullpronominal} / {/nameself=nameself}
|
||||
history: >
|
||||
Some people prefer not using any pronouns, instead being referred by name, initial,
|
||||
omitting pronouns with passive voice, or restructuring the sentence.
|
||||
See: {https://web.archive.org/web/20211214214513/https://twitter.com/lypomania/status/1290274971642544128=lypomania's guide},
|
||||
{https://web.archive.org/web/20230326021305/https://www.lgbtqia.wiki/wiki/Nullpronominal=Nullpronominal on LGBTA Wiki}
|
||||
routes: ['avoiding', 'no-pronouns', 'null', 'pronounless', 'nullpronominal', 'nameself']
|
||||
morphemes:
|
||||
pronoun_subject: '#'
|
||||
pronoun_object: '#'
|
||||
@ -36,7 +29,6 @@ pronouns:
|
||||
possessive_pronoun: '#''s'
|
||||
reflexive: '#self'
|
||||
examples: [':Andrea', ':S']
|
||||
routes: ['avoiding']
|
||||
ideas:
|
||||
-
|
||||
header: 'Use names or initials instead of pronouns'
|
||||
|
@ -76,6 +76,11 @@ pronouns:
|
||||
header: 'Interchangeable forms'
|
||||
raw: 'interchangeable'
|
||||
null:
|
||||
description: >
|
||||
Some people prefer not using any pronouns, instead being referred by name, initial,
|
||||
omitting pronouns with passive voice, or restructuring the sentence.
|
||||
See: {https://web.archive.org/web/20211214214513/https://twitter.com/lypomania/status/1290274971642544128=lypomania's guide},
|
||||
{https://web.archive.org/web/20230326021305/https://www.lgbtqia.wiki/wiki/Nullpronominal=Nullpronominal on LGBTA Wiki}
|
||||
button: 'Generate a link with a name or initial'
|
||||
base: 'Name or initial'
|
||||
emoji:
|
||||
|
@ -196,20 +196,12 @@ interface MultiplePronounsConfig {
|
||||
}
|
||||
|
||||
export type NullPronounsConfig = {
|
||||
/**
|
||||
* short description in the header (translated)
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* longer description as text (translated)
|
||||
*/
|
||||
history: string;
|
||||
/**
|
||||
* route paths for null pronoun usage (translated).
|
||||
* when empty, disables page
|
||||
* @default []
|
||||
*/
|
||||
routes?: string[];
|
||||
routes: string[];
|
||||
/**
|
||||
* different strategies of avoiding pronouns
|
||||
*/
|
||||
|
@ -91,12 +91,7 @@ pronouns:
|
||||
description: 'Einige nichtbinäre Menschen nutzen mehr als eine Form von Pronomen und sind damit einverstanden, mit irgendwelchen dieser Pronomen angesprochen zu werden.'
|
||||
examples: ['er&sie', 'er&dey', 'sie&xier']
|
||||
null:
|
||||
description: >
|
||||
{/name=Name} / {/initialen=Initialen} / {/keine-pronomen=keine Pronomen} / {/vermeiden=Pronomen vermeiden}
|
||||
history: >
|
||||
Einige Leute ziehen es vor, keine Pronomen zu verwenden.
|
||||
Stattdessen wird der Name oder der Initiale verwendet
|
||||
oder Sätze so umformuliert, dass diese keine geschlechtsspezifischen Pronomen enthalten.
|
||||
routes: ['name', 'initialen', 'keine-pronomen', 'vermeiden']
|
||||
morphemes:
|
||||
pronoun_n: '#'
|
||||
pronoun_d: '#'
|
||||
@ -144,7 +139,6 @@ pronouns:
|
||||
possessive_pronoun_pl_d: '#/[sxzß]$/i#’|#s'
|
||||
adverb_because: 'wegen #'
|
||||
examples: [':Andrea', ':A', ':S']
|
||||
routes: ['name', 'initialen', 'keine-pronomen', 'vermeiden']
|
||||
ideas:
|
||||
-
|
||||
header: 'Name oder Initialen anstelle von Pronomen'
|
||||
|
@ -77,11 +77,20 @@ pronouns:
|
||||
header: 'Austauschbare Formen'
|
||||
raw: 'austauschbar'
|
||||
null:
|
||||
header:
|
||||
name: 'Name'
|
||||
initialen: 'Initialen'
|
||||
keine-pronomen: 'keine Pronomen'
|
||||
vermeiden: 'Pronomen vermeiden'
|
||||
short:
|
||||
name: 'Name'
|
||||
initialen: 'Initialen'
|
||||
keine-pronomen: 'keine Pronomen'
|
||||
vermeiden: 'Pronomen vermeiden'
|
||||
description: >
|
||||
Einige Leute ziehen es vor, keine Pronomen zu verwenden.
|
||||
Stattdessen wird der Name oder der Initiale verwendet
|
||||
oder Sätze so umformuliert, dass diese keine geschlechtsspezifischen Pronomen enthalten.
|
||||
button: 'Generiere einen Link mit einem Namen oder einer Initiale'
|
||||
base: 'Name oder Initiale'
|
||||
emoji:
|
||||
|
@ -38,14 +38,7 @@ pronouns:
|
||||
Some like when others alternate between those forms when talking about them.
|
||||
examples: ['he&she', 'he&they', 'she&they']
|
||||
null:
|
||||
description: >
|
||||
{/avoiding=Avoiding gendered forms} / {/no-pronouns=no pronouns} / {/null=null pronouns} /
|
||||
{/pronounless=pronounless} / {/nullpronominal=nullpronominal} / {/nameself=nameself}
|
||||
history: >
|
||||
Some people prefer not using any pronouns, instead being referred by name, initial,
|
||||
omitting pronouns with passive voice, or restructuring the sentence.
|
||||
See: {https://web.archive.org/web/20211214214513/https://twitter.com/lypomania/status/1290274971642544128=lypomania's guide},
|
||||
{https://web.archive.org/web/20230326021305/https://www.lgbtqia.wiki/wiki/Nullpronominal=Nullpronominal on LGBTA Wiki}
|
||||
routes: ['avoiding', 'no-pronouns', 'null', 'pronounless', 'nullpronominal', 'nameself']
|
||||
morphemes:
|
||||
pronoun_subject: '#'
|
||||
pronoun_object: '#'
|
||||
@ -53,7 +46,6 @@ pronouns:
|
||||
possessive_pronoun: '#''s'
|
||||
reflexive: '#self'
|
||||
examples: [':Andrea', ':S']
|
||||
routes: ['avoiding', 'no-pronouns', 'null', 'pronounless', 'nullpronominal', 'nameself']
|
||||
ideas:
|
||||
-
|
||||
header: 'Use names or initials instead of pronouns'
|
||||
|
@ -76,8 +76,18 @@ pronouns:
|
||||
header: 'Interchangeable forms'
|
||||
raw: 'interchangeable'
|
||||
null:
|
||||
header:
|
||||
avoiding: 'Avoiding gendered forms'
|
||||
no-pronouns: 'no pronouns'
|
||||
null: 'null pronouns'
|
||||
short:
|
||||
no-pronouns: 'no pronouns'
|
||||
null: 'null pronouns'
|
||||
description: >
|
||||
Some people prefer not using any pronouns, instead being referred by name, initial,
|
||||
omitting pronouns with passive voice, or restructuring the sentence.
|
||||
See: {https://web.archive.org/web/20211214214513/https://twitter.com/lypomania/status/1290274971642544128=lypomania's guide},
|
||||
{https://web.archive.org/web/20230326021305/https://www.lgbtqia.wiki/wiki/Nullpronominal=Nullpronominal on LGBTA Wiki}
|
||||
button: 'Generate a link with a name or initial'
|
||||
base: 'Name or initial'
|
||||
emoji:
|
||||
|
@ -20,19 +20,11 @@ pronouns:
|
||||
Some like when others alternate between those forms when talking about them.
|
||||
examples: ['li&ŝi', 'li&ĝi', 'ĝi&ri']
|
||||
null:
|
||||
description: >
|
||||
{/avoiding=Avoiding gendered forms} / {/no-pronouns=no pronouns} / {/null=null pronouns} /
|
||||
{/pronounless=pronounless} / {/nullpronominal=nullpronominal} / {/nameself=nameself}
|
||||
history: >
|
||||
Some people prefer not using any pronouns, instead being referred by name, initial,
|
||||
omitting pronouns with passive voice, or restructuring the sentence.
|
||||
See: {https://web.archive.org/web/20211214214513/https://twitter.com/lypomania/status/1290274971642544128=lypomania's guide},
|
||||
{https://web.archive.org/web/20230326021305/https://www.lgbtqia.wiki/wiki/Nullpronominal=Nullpronominal on LGBTA Wiki}
|
||||
routes: ['avoiding']
|
||||
morphemes:
|
||||
pronoun_n: '#'
|
||||
pronoun_a: '#'
|
||||
examples: [':Andrea', ':S']
|
||||
routes: ['avoiding']
|
||||
ideas:
|
||||
-
|
||||
header: 'Use names or initials instead of pronouns'
|
||||
|
@ -17,11 +17,6 @@ pronouns:
|
||||
description: 'Muchas personas no binarias usan más de una forma intercambiablemente y se sienten cómodas con cualquiera de las opciones.'
|
||||
examples: ['él&ella', 'él&elle', 'ella&elle']
|
||||
null:
|
||||
description: >
|
||||
{/evitar=Evitar formas de género}
|
||||
history: >
|
||||
Algunas personas prefieren no usar pronombres, sino ser referidas por su nombre, por su inicial,
|
||||
omitir oraciones en voz activa o reestructurar la frase.
|
||||
routes: ['evitar']
|
||||
ideas:
|
||||
-
|
||||
|
@ -88,6 +88,11 @@ pronouns:
|
||||
sentence: >
|
||||
Puedes modificar los links predeterminados para que la URL se lea como esta frase; por ejemplo:
|
||||
null:
|
||||
header:
|
||||
evitar: 'Evitar formas de género'
|
||||
description: >
|
||||
Algunas personas prefieren no usar pronombres, sino ser referidas por su nombre, por su inicial,
|
||||
omitir oraciones en voz activa o reestructurar la frase.
|
||||
button: 'Generar un enlace con un nombre o una inicial'
|
||||
base: 'Nombre o inicial'
|
||||
emoji:
|
||||
|
@ -19,60 +19,7 @@ pronouns:
|
||||
and are fine with being called either of them.
|
||||
Some like when others alternate between those forms when talking about them.
|
||||
examples: ['tema&temake', 'tema&see']
|
||||
null:
|
||||
description: >
|
||||
{/avoiding=Avoiding gendered forms} / {/no-pronouns=no pronouns} / {/null=null pronouns} /
|
||||
{/pronounless=pronounless} / {/nullpronominal=nullpronominal} / {/nameself=nameself}
|
||||
history: >
|
||||
Some people prefer not using any pronouns, instead being referred by name, initial,
|
||||
omitting pronouns with passive voice, or restructuring the sentence.
|
||||
See: {https://web.archive.org/web/20211214214513/https://twitter.com/lypomania/status/1290274971642544128=lypomania's guide},
|
||||
{https://web.archive.org/web/20230326021305/https://www.lgbtqia.wiki/wiki/Nullpronominal=Nullpronominal on LGBTA Wiki}
|
||||
morphemes:
|
||||
pronoun_subject: '#'
|
||||
pronoun_object: '#'
|
||||
possessive_determiner: '#''s'
|
||||
possessive_pronoun: '#''s'
|
||||
reflexive: '#self'
|
||||
examples: [':Andrea', ':S']
|
||||
routes: ['avoiding']
|
||||
ideas:
|
||||
-
|
||||
header: 'Use names or initials instead of pronouns'
|
||||
normative: true
|
||||
examples:
|
||||
- ['I talked to him yesterday', 'I talked to {/:Sky=Sky} yesterday']
|
||||
- ['She is really beautiful', '{/:Soph=Soph} is really beautiful']
|
||||
-
|
||||
- 'Her graduation starts soon'
|
||||
- '{/:J=J}''s graduation starts soon'
|
||||
-
|
||||
header: 'Passive voice'
|
||||
normative: true
|
||||
examples:
|
||||
- ['He answered the phone', 'The phone was answered']
|
||||
-
|
||||
- 'Wen takes good care of her cat'
|
||||
- 'Wen''s cat is well cared for'
|
||||
-
|
||||
header: 'Rephrasing the sentence (circumlocution)'
|
||||
normative: true
|
||||
examples:
|
||||
- ['Lior did it all by himself', 'Lior did it all without any help']
|
||||
- ['Gael talks in his sleep', 'Gael talks while sleeping']
|
||||
-
|
||||
header: 'Replacing a pronoun with a descriptive noun or phrase'
|
||||
normative: true
|
||||
examples:
|
||||
- ['She landed the plane safely', 'The pilot landed the plane safely']
|
||||
- ['This is Lea, she is into painting', 'This is Lea. My friend is into painting']
|
||||
- ['She argues that…', 'The person who started this discussion argues that…']
|
||||
-
|
||||
header: 'Dropping pronouns'
|
||||
normative: true
|
||||
examples:
|
||||
- ['Did you buy Tex her gift?', 'Did you buy Tex a gift?']
|
||||
- ['Yes, I bought it for her. I will give it to her tomorrow.', 'Yes, I bought it. I will give it tomorrow.']
|
||||
null: false
|
||||
emoji: false
|
||||
others: 'Teised asesõnad'
|
||||
|
||||
|
@ -19,70 +19,8 @@ pronouns:
|
||||
and are fine with being called either of them.
|
||||
Some like when others alternate between those forms when talking about them.
|
||||
examples: ['he&she', 'he&they', 'she&they']
|
||||
null:
|
||||
description: >
|
||||
{/avoiding=Avoiding gendered forms} / {/no-pronouns=no pronouns} / {/null=null pronouns} /
|
||||
{/pronounless=pronounless} / {/nullpronominal=nullpronominal} / {/nameself=nameself}
|
||||
history: >
|
||||
Some people prefer not using any pronouns, instead being referred by name, initial,
|
||||
omitting pronouns with passive voice, or restructuring the sentence.
|
||||
See: {https://web.archive.org/web/20211214214513/https://twitter.com/lypomania/status/1290274971642544128=lypomania's guide},
|
||||
{https://web.archive.org/web/20230326021305/https://www.lgbtqia.wiki/wiki/Nullpronominal=Nullpronominal on LGBTA Wiki}
|
||||
morphemes:
|
||||
pronoun_subject: '#'
|
||||
pronoun_object: '#'
|
||||
possessive_determiner: '#''s'
|
||||
possessive_pronoun: '#''s'
|
||||
reflexive: '#self'
|
||||
examples: [':Andrea', ':S']
|
||||
routes: ['avoiding']
|
||||
ideas:
|
||||
-
|
||||
header: 'Use names or initials instead of pronouns'
|
||||
normative: true
|
||||
examples:
|
||||
- ['I talked to him yesterday', 'I talked to {/:Sky=Sky} yesterday']
|
||||
- ['She is really beautiful', '{/:Soph=Soph} is really beautiful']
|
||||
-
|
||||
- 'Her graduation starts soon'
|
||||
- '{/:J=J}''s graduation starts soon'
|
||||
-
|
||||
header: 'Passive voice'
|
||||
normative: true
|
||||
examples:
|
||||
- ['He answered the phone', 'The phone was answered']
|
||||
-
|
||||
- 'Wen takes good care of her cat'
|
||||
- 'Wen''s cat is well cared for'
|
||||
-
|
||||
header: 'Rephrasing the sentence (circumlocution)'
|
||||
normative: true
|
||||
examples:
|
||||
- ['Lior did it all by himself', 'Lior did it all without any help']
|
||||
- ['Gael talks in his sleep', 'Gael talks while sleeping']
|
||||
-
|
||||
header: 'Replacing a pronoun with a descriptive noun or phrase'
|
||||
normative: true
|
||||
examples:
|
||||
- ['She landed the plane safely', 'The pilot landed the plane safely']
|
||||
- ['This is Lea, she is into painting', 'This is Lea. My friend is into painting']
|
||||
- ['She argues that…', 'The person who started this discussion argues that…']
|
||||
-
|
||||
header: 'Dropping pronouns'
|
||||
normative: true
|
||||
examples:
|
||||
- ['Did you buy Tex her gift?', 'Did you buy Tex a gift?']
|
||||
- ['Yes, I bought it for her. I will give it to her tomorrow.', 'Yes, I bought it. I will give it tomorrow.']
|
||||
emoji:
|
||||
description: 'Emojiself pronouns'
|
||||
history: '{https://lgbta.wikia.org/wiki/Emojiself_Pronouns=Emojiself} pronouns are intended for online communication and not supposed to be pronounced.'
|
||||
morphemes:
|
||||
pronoun_subject: '#'
|
||||
pronoun_object: '#'
|
||||
possessive_determiner: '#''s'
|
||||
possessive_pronoun: '#''s'
|
||||
reflexive: '#self'
|
||||
examples: ['💫', '💙']
|
||||
null: false
|
||||
emoji: false
|
||||
mirror:
|
||||
route: 'mirror'
|
||||
name: 'Mirror pronouns / Mirrorpronominal'
|
||||
|
@ -22,19 +22,12 @@ pronouns:
|
||||
alternando fra queste.
|
||||
examples: ['lui&lei', 'lui&ləi', 'lei&ləi']
|
||||
null:
|
||||
description: >
|
||||
{/avoiding=Evitare sostantivi che siano “genderati”} / {/no-pronouns=Niente pronomi} / {/null=Nessun pronome} / {/nameself=Il proprio nome}
|
||||
history: >
|
||||
Alcune persone preferiscono non usare pronomi, preferiscono invece essere chiamate col proprio nome, l’iniziale,
|
||||
omettendo pronomi con voce passiva o ristrutturando la frase.
|
||||
Guarda: {https://twitter.com/lypomania/status/1290274971642544128=Guida di lypomania},
|
||||
{https://lgbta.wikia.org/wiki/Nullpronominal=”Senza pronomi” su wiki LGBTQIA+}
|
||||
routes: ['evitare', 'niente-pronomi', 'nessun-pronome', 'nome']
|
||||
morphemes:
|
||||
pronoun_n: '#'
|
||||
pronoun_d: 'a(d) #'
|
||||
pronoun_a: '#'
|
||||
examples: [':Di', ':Andrea']
|
||||
routes: ['Evitare']
|
||||
ideas:
|
||||
-
|
||||
header: 'Usare nomi o iniziali invece che i pronomi'
|
||||
|
@ -72,6 +72,20 @@ pronouns:
|
||||
header: 'forme intercambiabili'
|
||||
raw: 'intercambiabile'
|
||||
null:
|
||||
header:
|
||||
evitare: 'Evitare sostantivi che siano “genderati”'
|
||||
niente-pronomi: 'Niente pronomi'
|
||||
nessun-pronome: 'Nessun pronome'
|
||||
nome: 'Il proprio nome'
|
||||
short:
|
||||
niente-pronomi: 'niente pronomi'
|
||||
nessun-pronome: 'nessun pronome'
|
||||
nome: 'nome'
|
||||
description: >
|
||||
Alcune persone preferiscono non usare pronomi, preferiscono invece essere chiamate col proprio nome,
|
||||
l’iniziale, omettendo pronomi con voce passiva o ristrutturando la frase.
|
||||
Guarda: {https://twitter.com/lypomania/status/1290274971642544128=Guida di lypomania},
|
||||
{https://lgbta.wikia.org/wiki/Nullpronominal=”Senza pronomi” su wiki LGBTQIA+}
|
||||
button: 'Genera un collegamento con un nome o un''iniziale'
|
||||
base: 'Nome o iniziale'
|
||||
emoji:
|
||||
|
@ -20,8 +20,7 @@ pronouns:
|
||||
어떤 사람들은 다른 사람들이 그들에 대해 이야기할 때 이러한 형태를 번갈아 가며 사용하는 것을 좋아합니다.
|
||||
examples: ['그와 그녀', '그와 그들', '그녀와 그들']
|
||||
null:
|
||||
description: '대명사 없음 / 무효 대명사 / 자기 이름'
|
||||
history: '어떤 사람들은 대명사를 사용하지 않고 대신 이름, 이니셜 또는 수동태로 대명사를 생략하는 것을 선호합니다({https://web.archive.org/web/20230326021305/https://www.lgbtqia.wiki/wiki/Nullpronominal=널 의례적인}.'
|
||||
routes: ['avoiding']
|
||||
morphemes:
|
||||
pronoun: '#'
|
||||
examples: [':안드레아', ':S']
|
||||
|
@ -78,6 +78,12 @@ pronouns:
|
||||
많은 사람들한테는 똑바로 쓴 대명사가 중요하는데,
|
||||
어떤 사람들은 아무 대명사가 괜찮습니다.
|
||||
options: '옵션을 확인하세요. [share]{/pronouns=here}.'
|
||||
null:
|
||||
header:
|
||||
avoiding: '대명사 없음 / 무효 대명사 / 자기 이름'
|
||||
description: >
|
||||
어떤 사람들은 대명사를 사용하지 않고 대신 이름, 이니셜 또는 수동태로 대명사를 생략하는 것을 선호합니다
|
||||
({https://web.archive.org/web/20230326021305/https://www.lgbtqia.wiki/wiki/Nullpronominal=널 의례적인}.
|
||||
others: '기타 양식'
|
||||
othersRaw: '기타'
|
||||
or: '아님'
|
||||
|
@ -17,8 +17,7 @@ pronouns:
|
||||
description: 'Veel non-binaire personen gebruiken afwisselend meer dan een vorm en vinden het prima dat je beiden gebruikt.'
|
||||
examples: ['hij&zij', 'hij&hen', 'zij&hen']
|
||||
null:
|
||||
description: 'Geen voornaamwoorden'
|
||||
history: 'Sommige mensen gebruiken liever geen enkel voornaamwoord, en worden liever aangesproken met hun naam, initialen, of willen liever dat je voornaamwoorden ontwijkt door passieve zinnen te maken.'
|
||||
routes: ['geen-voornaamwoorden']
|
||||
morphemes:
|
||||
nominative: '#'
|
||||
accusative: '#'
|
||||
|
@ -77,6 +77,14 @@ pronouns:
|
||||
vinden anderen het geen probleem om op wat voor manier dan ook aangesproken te worden
|
||||
- zolang de context duidelijk maakt wie hiermee wordt gerefereerd.
|
||||
options: 'bekijk de mogelijkheden [share]{/voornaamwoorden=hier}.'
|
||||
null:
|
||||
header:
|
||||
geen-voornaamwoorden: 'Geen voornaamwoorden'
|
||||
short:
|
||||
geen-voornaamwoorden: 'geen voornaamwoorden'
|
||||
description: >
|
||||
Sommige mensen gebruiken liever geen enkel voornaamwoord, en worden liever aangesproken met
|
||||
hun naam, initialen, of willen liever dat je voornaamwoorden ontwijkt door passieve zinnen te maken.
|
||||
others: 'Andere vormen'
|
||||
othersRaw: 'andere'
|
||||
or: 'of'
|
||||
|
@ -48,14 +48,6 @@ pronouns:
|
||||
honorific: '#'
|
||||
examples: ['💫', '💙', '🐿']
|
||||
null:
|
||||
description: '{/unikanie=Unikanie form nacechowanych płciowo}'
|
||||
history: >
|
||||
Niektóre osoby niebinarne preferują używanie form unikających precyzowania płci
|
||||
zamiast tych nacechowanych czy to binarnie czy niebinarnie.
|
||||
Warto też używać ich mówiąc o osobach, których płci nie znamy.
|
||||
Choć w silnie zgenderyzowanym języku polskim jest to względnie trudne do zrobienia
|
||||
i może lekko zmieniać znaczenie zdania,
|
||||
to jednak wcale nie jest niemożliwe.
|
||||
routes: ['unikanie']
|
||||
ideas:
|
||||
-
|
||||
|
@ -100,6 +100,15 @@ pronouns:
|
||||
Oprócz domyślnych linków możesz też podlinkować do swoich zaimków tak,
|
||||
aby URL czytało się niemal jak zdanie; na przykład:
|
||||
null:
|
||||
header:
|
||||
unikanie: 'Unikanie form nacechowanych płciowo'
|
||||
description: >
|
||||
Niektóre osoby niebinarne preferują używanie form unikających precyzowania płci
|
||||
zamiast tych nacechowanych czy to binarnie czy niebinarnie.
|
||||
Warto też używać ich mówiąc o osobach, których płci nie znamy.
|
||||
Choć w silnie zgenderyzowanym języku polskim jest to względnie trudne do zrobienia
|
||||
i może lekko zmieniać znaczenie zdania,
|
||||
to jednak wcale nie jest niemożliwe.
|
||||
button: 'Wygeneruj link z imieniem albo inicjałem'
|
||||
base: 'Imię lub inicjał'
|
||||
emoji:
|
||||
|
@ -20,13 +20,7 @@ pronouns:
|
||||
Unora le place când alții alternează între aceste forme.
|
||||
examples: ['el&ea', 'el&ei', 'ea&ei']
|
||||
null:
|
||||
description: >
|
||||
{/evitare=Evitarea pronumelor cu gen} / {/fara-pronume=fără pronume} / {/nume=doar nume}
|
||||
history: >
|
||||
Unele persoane preferă să nu folosească niciun pronume, fiind referiți după nume, după inițială sau
|
||||
evitând pronumele folosind voce pasivă sau restructurând propozițiile.
|
||||
Vezi: {https://web.archive.org/web/20211214214513/https://twitter.com/lypomania/status/1290274971642544128=ghidul lui lypomania (în lb. engleză)},
|
||||
{https://web.archive.org/web/20230326021305/https://www.lgbtqia.wiki/wiki/Nullpronominal=Nul-pronominal pe LGBTA Wiki (în lb. engleză)}
|
||||
routes: ['evitare', 'fara-pronume', 'nume']
|
||||
morphemes:
|
||||
pronoun_n: '#'
|
||||
pronoun_dg: 'lui #'
|
||||
@ -34,7 +28,6 @@ pronouns:
|
||||
first_article: 'L-am'
|
||||
second_article: ''
|
||||
examples: [':Andrea', ':S']
|
||||
routes: ['evitare', 'fara-pronume', 'nume']
|
||||
ideas:
|
||||
-
|
||||
header: 'Folosește nume sau inițiale în loc de pronume'
|
||||
|
@ -79,6 +79,18 @@ pronouns:
|
||||
pentru ei, pe alții nu îi deranjează să le fie adresați în orice fel – atâta timp cât îți poți da seama din
|
||||
context despre cine se vorbește.
|
||||
options: 'vezi pronumele [share]{/pronume=aici}.'
|
||||
null:
|
||||
header:
|
||||
evitare: 'Evitarea pronumelor cu gen'
|
||||
fara-pronume: 'fără pronume'
|
||||
nume: 'doar nume'
|
||||
short:
|
||||
fara-pronume: 'fără pronume'
|
||||
description: >
|
||||
Unele persoane preferă să nu folosească niciun pronume, fiind referiți după nume, după inițială sau
|
||||
evitând pronumele folosind voce pasivă sau restructurând propozițiile.
|
||||
Vezi: {https://web.archive.org/web/20211214214513/https://twitter.com/lypomania/status/1290274971642544128=ghidul lui lypomania (în lb. engleză)},
|
||||
{https://web.archive.org/web/20230326021305/https://www.lgbtqia.wiki/wiki/Nullpronominal=Nul-pronominal pe LGBTA Wiki (în lb. engleză)}
|
||||
others: 'Alte forme'
|
||||
othersRaw: 'altele'
|
||||
or: 'sau'
|
||||
|
@ -30,11 +30,6 @@ pronouns:
|
||||
- 'Персона В использует {/она=она/её}, так что, говоря о персоне А, она использует в её отношениии местоимения “она/её”.'
|
||||
- 'Персона С использует {/зе=зе/зир} и {/фае=фае/фер}, так что, когда зе говорит о персоне А, фае может использовать к фер и фае/фер, и зе/зир.'
|
||||
null:
|
||||
description: '{/avoiding=Избегание гендерных форм}'
|
||||
history: >
|
||||
Некоторые небинарные персоны предпочитают использовать формы, в которых не указывается гендер, вместо форм с гендерными окончаниями.
|
||||
Также предпочтительно использовать такие формы, говоря о персонах, чьи местоимения неизвестны.
|
||||
Так как в русском языке род играет важную роль, полностью избавиться от гендерных окончаний довольно сложно, но не невозможно.
|
||||
routes: ['avoiding']
|
||||
ideas:
|
||||
-
|
||||
|
@ -66,6 +66,12 @@ pronouns:
|
||||
header: 'Заменяемые формы'
|
||||
raw: 'заменяемое'
|
||||
null:
|
||||
header:
|
||||
avoiding: 'Избегание гендерных форм'
|
||||
description: >
|
||||
Некоторые небинарные персоны предпочитают использовать формы, в которых не указывается гендер, вместо форм с гендерными окончаниями.
|
||||
Также предпочтительно использовать такие формы, говоря о персонах, чьи местоимения неизвестны.
|
||||
Так как в русском языке род играет важную роль, полностью избавиться от гендерных окончаний довольно сложно, но не невозможно.
|
||||
button: 'Сгенерировать ссылку с именем или инициалами'
|
||||
base: 'Имя или инициалы'
|
||||
emoji:
|
||||
|
@ -31,11 +31,6 @@ pronouns:
|
||||
- 'Людина В використовує {/вона=вона/її}, отже, говорячи про людину А, вона використовує до її відношенні займенник “вона/її”.'
|
||||
- 'Людина С використовує {/зі=зі/зір} та {/фае=фае/фер}, отже, коли зі говорить про людину А, фае може використовувати до фер і фае/фер, і зе/зір.'
|
||||
null:
|
||||
description: '{/avoiding=Уникнення ґендерних форм}'
|
||||
history: >
|
||||
Деякі небінарні люди воліють використовувати форми, в яких не вказується ґендер замість форм з ґендерними закінченнями.
|
||||
Також важливо використовувати такі форми, говорячи про людей, чиї займенники невідомі.
|
||||
Так як у українській мові рід грає важливу роль, повністю позбавиться ґендерних закінчень досить складно, але не неможливо.
|
||||
routes: ['avoiding']
|
||||
ideas:
|
||||
-
|
||||
|
@ -78,6 +78,12 @@ pronouns:
|
||||
or: 'або'
|
||||
grammarTable: 'Таблиця'
|
||||
null:
|
||||
header:
|
||||
avoiding: 'Уникнення ґендерних форм'
|
||||
description: >
|
||||
Деякі небінарні люди воліють використовувати форми, в яких не вказується ґендер замість форм з ґендерними закінченнями.
|
||||
Також важливо використовувати такі форми, говорячи про людей, чиї займенники невідомі.
|
||||
Так як у українській мові рід грає важливу роль, повністю позбавиться ґендерних закінчень досить складно, але не неможливо.
|
||||
button: 'Згенерувати посилання з іменем або ініціалом'
|
||||
base: 'Імʼя або ініціал'
|
||||
emoji:
|
||||
|
@ -20,16 +20,11 @@ pronouns:
|
||||
Một vài người lại muốn người khác dùng đan xen các danh xưng khi đang nói về họ.
|
||||
examples: ['anh&cô', 'anh&họ', 'cô&họ']
|
||||
null:
|
||||
description: >
|
||||
{/tránh=Tránh các từ chỉ giới tính} / {/bỏ-danh-xưng=không dùng danh xưng} / {/tên=danh xưng là tên}
|
||||
history: >
|
||||
Có người không muốn dùng bất cứ danh xưng nào, thay vào đó là tên, tên viết tắt/kí hiệu,
|
||||
việc lược bỏ danh xưng bằng cấu trúc câu bị động, hoặc thay đổi cấu trúc câu nói.
|
||||
routes: ['tránh', 'bỏ-danh-xưng', 'tên']
|
||||
morphemes:
|
||||
3rd_person: '#'
|
||||
2nd_person: '#'
|
||||
examples: [':Vanh', ':A']
|
||||
routes: ['tránh', 'bỏ-danh-xưng', 'tên']
|
||||
ideas:
|
||||
-
|
||||
header: 'Dùng tên hoặc kí hiệu thay cho danh xưng'
|
||||
|
@ -75,6 +75,15 @@ pronouns:
|
||||
header: 'Danh xưng hoán đổi'
|
||||
raw: 'hoán đổi'
|
||||
null:
|
||||
header:
|
||||
tránh: 'Tránh các từ chỉ giới tính'
|
||||
bỏ-danh-xưng: 'không dùng danh xưng'
|
||||
tên: 'danh xưng là tên'
|
||||
short:
|
||||
bỏ-danh-xưng: 'bỏ danh xưng'
|
||||
description: >
|
||||
Có người không muốn dùng bất cứ danh xưng nào, thay vào đó là tên, tên viết tắt/kí hiệu,
|
||||
việc lược bỏ danh xưng bằng cấu trúc câu bị động, hoặc thay đổi cấu trúc câu nói.
|
||||
button: 'Tạo đường link với tên hoặc tên viết tắt'
|
||||
base: 'Tên hoặc tên viết tắt'
|
||||
generated: 'Những danh xưng này đã được tạo bằng trình tạo danh xưng. Tập thể pronouns.page không chịu bất kì trách nhiệm gì về những danh xưng này.'
|
||||
|
@ -17,10 +17,7 @@ pronouns:
|
||||
name: 'Interchangeable forms'
|
||||
description: 'Many nonbinary people use more than one form interchangeably and are fine with being called either of them.'
|
||||
examples: ['ער&זי', 'זי&זיי', 'ער&זיי']
|
||||
null:
|
||||
description: 'No pronouns / null pronouns / pronounless / nameself'
|
||||
history: 'Some people prefer not using any pronouns, instead being referred by name, initial, or by omitting pronouns with passive voice, see: {https://web.archive.org/web/20230326021305/https://www.lgbtqia.wiki/wiki/Nullpronominal=nullpronominal}.'
|
||||
examples: [':Andrea', ':S']
|
||||
null: false
|
||||
emoji: false
|
||||
others: 'Other pronouns'
|
||||
|
||||
|
@ -156,6 +156,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<OpinionListInput v-model="formData.pronouns" :validation="validatePronoun" :custom-opinions="formData.opinions" :maxitems="128" :maxlength="192" />
|
||||
<PronounsIndex />
|
||||
</template>
|
||||
|
||||
<template #description-header>
|
||||
@ -382,6 +383,7 @@ import { buildList, isValidLink } from '~/src/helpers.ts';
|
||||
import { pronouns, pronounLibrary } from '~/src/data.ts';
|
||||
import { buildPronoun, buildPronounUsage } from '~/src/buildPronoun.ts';
|
||||
import { birthdateRange, formatDate, parseDate } from '~/src/birthdate.ts';
|
||||
import { addPronounInjectionKey } from '~/src/injectionKeys.ts';
|
||||
import opinions from '~/src/opinions.ts';
|
||||
import { buildCalendar } from '~/src/calendar/calendar.ts';
|
||||
import type { Config } from '~/locale/config.ts';
|
||||
@ -564,6 +566,10 @@ export default defineComponent({
|
||||
const { isDark } = useDark();
|
||||
const { recommendedLinkProviders } = useLinkUtils();
|
||||
|
||||
provide(addPronounInjectionKey, (pronoun) => {
|
||||
formData.value.pronouns.push({ value: pronoun, opinion: 'meh' });
|
||||
});
|
||||
|
||||
let profilesData;
|
||||
if (store.user) {
|
||||
profilesData = (await useFetch<{
|
||||
|
@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { buildAnyPronounsList } from '~/src/buildPronoun.ts';
|
||||
import { ExampleCategory } from '~/src/classes.ts';
|
||||
import { examples, pronouns, pronounLibrary } from '~/src/data.ts';
|
||||
import type { Pronoun, MergedPronounGroup } from '~/src/classes.ts';
|
||||
|
||||
|
||||
definePageMeta({
|
||||
translatedPaths: (config) => {
|
||||
if (!config.pronouns.enabled || !config.pronouns.any) {
|
||||
@ -22,7 +22,6 @@ definePageMeta({
|
||||
|
||||
const { $translator: translator } = useNuxtApp();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const config = useConfig();
|
||||
const groupKey = route.params.group as string;
|
||||
|
||||
@ -44,24 +43,8 @@ useSimpleHead({
|
||||
}, translator);
|
||||
|
||||
|
||||
const comprehensive = computed({
|
||||
get() {
|
||||
return config.pronouns.comprehensive ? Object.hasOwn(route.query, config.pronouns.comprehensive) : false;
|
||||
},
|
||||
set(value) {
|
||||
if (value === comprehensive.value || !config.pronouns.comprehensive) {
|
||||
// prevent warning that $router.replace has no effect
|
||||
return;
|
||||
}
|
||||
const query = structuredClone(route.query);
|
||||
if (value) {
|
||||
query[config.pronouns.comprehensive] = null;
|
||||
} else {
|
||||
delete query[config.pronouns.comprehensive];
|
||||
}
|
||||
router.replace({ query });
|
||||
},
|
||||
});
|
||||
const comprehensive = useComprehensive();
|
||||
|
||||
const pronounsChoice = computed(() => {
|
||||
if (!pronounGroups.length) {
|
||||
return Object.values(pronouns).filter((p) => !p.hidden);
|
||||
@ -73,6 +56,8 @@ const pronounsChoice = computed(() => {
|
||||
}
|
||||
return Object.values(choice).filter((p) => !p.hidden);
|
||||
});
|
||||
|
||||
const anyPronouns = buildAnyPronounsList(config, pronounLibrary);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -97,12 +82,6 @@ const pronounsChoice = computed(() => {
|
||||
<Spelling :text="short" />
|
||||
</strong>
|
||||
</h2>
|
||||
<p class="h6 small text-center mb-0 mt-2">
|
||||
<em>
|
||||
<T>pronouns.any.description</T>
|
||||
(<T>pronouns.any.options</T>)
|
||||
</em>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -124,14 +103,25 @@ const pronounsChoice = computed(() => {
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="alert alert-light">
|
||||
<Icon v="info-circle" />
|
||||
<T>pronouns.any.description</T>
|
||||
<em>(<T>pronouns.any.options</T>)</em>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<PronounGroup
|
||||
v-for="pronounGroup in pronounGroups"
|
||||
:key="pronounGroup.group.name"
|
||||
:pronoun-group="pronounGroup"
|
||||
:pronoun-group="pronounGroup.group"
|
||||
:pronouns="pronounGroup.groupPronouns"
|
||||
/>
|
||||
|
||||
<AdPlaceholder :phkey="['content-0', 'content-mobile-0']" />
|
||||
|
||||
<PronounGroup :pronouns="anyPronouns" />
|
||||
|
||||
<section>
|
||||
<Share :title="`${$t('pronouns.intro')}: ${$t('pronouns.any.short')}`" />
|
||||
</section>
|
||||
|
@ -1,4 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { shortForVariant } from '~/src/buildPronoun.ts';
|
||||
|
||||
definePageMeta({
|
||||
translatedPaths: (config) => {
|
||||
if (!config.pronouns.enabled || !config.pronouns.ask) {
|
||||
@ -15,10 +17,10 @@ if (!config.pronouns.enabled || !config.pronouns.ask) {
|
||||
|
||||
const { $translator: translator } = useNuxtApp();
|
||||
const route = useRoute();
|
||||
const variant = route.meta.variant;
|
||||
const variant = route.meta.variant as string;
|
||||
|
||||
useSimpleHead({
|
||||
title: translator.translate(`pronouns.ask.header.${variant}`),
|
||||
title: `${translator.translate('pronouns.intro')}: ${shortForVariant('ask', variant, translator)}`,
|
||||
description: translator.translate('pronouns.ask.description'),
|
||||
banner: `api/banner/${variant}.png`,
|
||||
}, translator);
|
||||
@ -34,13 +36,9 @@ useSimpleHead({
|
||||
<section>
|
||||
<div class="alert alert-primary">
|
||||
<h2 class="text-center mb-0">
|
||||
<strong><T>pronouns.ask.header.{{ variant }}</T></strong>
|
||||
<strong><Spelling :text="shortForVariant('ask', variant, translator)" /></strong>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="alert alert-light">
|
||||
<Icon v="info-circle" />
|
||||
<T>pronouns.ask.description</T>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
@ -55,27 +53,19 @@ useSimpleHead({
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<AdPlaceholder :phkey="['content-0', 'content-mobile-0']" />
|
||||
|
||||
<section>
|
||||
<ul class="list-group mt-4">
|
||||
<li class="list-group-item">
|
||||
<ul>
|
||||
<li v-for="otherVariant in config.pronouns.ask.routes" :key="otherVariant">
|
||||
<nuxt-link :to="{ name: `pronouns-${otherVariant}` }">
|
||||
<strong><T>pronouns.ask.short.{{ otherVariant }}</T></strong>
|
||||
</nuxt-link>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<nuxt-link :to="{ name: 'pronouns' }" class="list-group-item list-group-item-action text-center">
|
||||
<Icon v="ellipsis-h-alt" />
|
||||
</nuxt-link>
|
||||
</ul>
|
||||
<div class="alert alert-light">
|
||||
<Icon v="info-circle" />
|
||||
<T>pronouns.ask.description</T>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<AdPlaceholder :phkey="['content-0', 'content-mobile-0']" />
|
||||
|
||||
<PronounGroup :pronouns="config.pronouns.ask.routes" />
|
||||
|
||||
<section>
|
||||
<Share :title="`${$t('pronouns.intro')}: ${$t('pronouns.ask.description')}`" />
|
||||
<Share :title="`${$t('pronouns.intro')}: ${shortForVariant('ask', variant, translator)}`" />
|
||||
</section>
|
||||
|
||||
<Separator icon="info" />
|
||||
|
@ -1,53 +1,82 @@
|
||||
<script setup lang="ts">
|
||||
import { useNuxtApp } from 'nuxt/app';
|
||||
import useConfig from '~/composables/useConfig.ts';
|
||||
import useSimpleHead from '~/composables/useSimpleHead.ts';
|
||||
import { shortForVariant } from '~/src/buildPronoun.ts';
|
||||
|
||||
definePageMeta({
|
||||
translatedPaths: (config) => {
|
||||
if (!config.pronouns.enabled || !config.pronouns.null || !config.pronouns.null.routes) {
|
||||
return [];
|
||||
}
|
||||
return withPronounPrefixesWithVariant(config, config.pronouns.null.routes);
|
||||
},
|
||||
});
|
||||
|
||||
const { $translator: translator } = useNuxtApp();
|
||||
const route = useRoute();
|
||||
const variant = route.meta.variant as string;
|
||||
const config = useConfig();
|
||||
|
||||
if (!config.pronouns.enabled || !config.pronouns.null) {
|
||||
throw new Error('config.pronouns.null is disabled');
|
||||
}
|
||||
|
||||
useSimpleHead({
|
||||
title: `${translator.translate('pronouns.intro')}: ${shortForVariant('null', variant, translator)}`,
|
||||
description: translator.translate('pronouns.null.description'),
|
||||
banner: `api/banner${route.path.replace(/\/$/, '')}.png`,
|
||||
}, translator);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page>
|
||||
<Page v-if="config.pronouns.enabled && config.pronouns.null && config.pronouns.null.routes">
|
||||
<h2>
|
||||
{{ clearLinkedText(config.pronouns.null.description) }}
|
||||
<Icon v="tag" />
|
||||
<T>pronouns.intro</T><T>quotation.colon</T>
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
<LinkedText :text="config.pronouns.null.history" />
|
||||
</p>
|
||||
<section>
|
||||
<div class="alert alert-primary">
|
||||
<h2 class="text-center mb-0">
|
||||
<strong>
|
||||
<Spelling :text="shortForVariant('null', variant, translator)" />
|
||||
</strong>
|
||||
</h2>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="alert alert-light">
|
||||
<Icon v="info-circle" />
|
||||
<T>pronouns.null.description</T>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<AdPlaceholder :phkey="['content-0', 'content-mobile-0']" />
|
||||
|
||||
<section>
|
||||
<PronounsNullGenerator />
|
||||
</section>
|
||||
|
||||
<Avoiding />
|
||||
|
||||
<PronounGroup :pronouns="config.pronouns.null.routes" />
|
||||
|
||||
<section>
|
||||
<Share :title="`${$t('pronouns.intro')}: ${shortForVariant('null', variant, translator)}`" />
|
||||
</section>
|
||||
|
||||
<AdPlaceholder :phkey="['content-1', 'content-mobile-1']" />
|
||||
|
||||
<Separator icon="info" />
|
||||
<section class="mb-0">
|
||||
<h2 class="h4">
|
||||
<Icon v="info-circle" />
|
||||
<T>home.whatisit</T>
|
||||
</h2>
|
||||
<T>home.about</T>
|
||||
<Homepage align="center" />
|
||||
</section>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useNuxtApp } from 'nuxt/app';
|
||||
import { clearLinkedText } from '~/src/helpers.ts';
|
||||
import useConfig from '~/composables/useConfig.ts';
|
||||
import useSimpleHead from '~/composables/useSimpleHead.ts';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
definePageMeta({
|
||||
translatedPaths: (config) => {
|
||||
if (!config.pronouns.enabled || !config.pronouns.null || !config.pronouns.null.routes) {
|
||||
return [];
|
||||
}
|
||||
const nullPaths = config.pronouns.null.routes.map((route) => `/${encodeURIComponent(route)}`);
|
||||
return withPronounPrefixes(config, nullPaths);
|
||||
},
|
||||
});
|
||||
|
||||
const { $translator: translator } = useNuxtApp();
|
||||
const route = useRoute();
|
||||
const config = useConfig();
|
||||
|
||||
useSimpleHead({
|
||||
title: config.pronouns.null.description,
|
||||
description: config.pronouns.null.history,
|
||||
banner: `api/banner${route.path.replace(/\/$/, '')}.png`,
|
||||
}, translator);
|
||||
|
||||
return {
|
||||
config,
|
||||
clearLinkedText,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -1,3 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import Suggested from '~/data/pronouns/Suggested.vue';
|
||||
import useConfig from '~/composables/useConfig.ts';
|
||||
|
||||
definePageMeta({
|
||||
translatedPaths: (config) => translatedPathByConfigModule(config.pronouns),
|
||||
});
|
||||
|
||||
const config = useConfig();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page v-if="config.pronouns.enabled">
|
||||
<section>
|
||||
@ -21,318 +32,7 @@
|
||||
|
||||
<AdPlaceholder :phkey="['content-0', 'content-mobile-0']" />
|
||||
|
||||
<ul class="list-group mt-4">
|
||||
<template v-for="[group, groupPronouns] in pronounLibrary.split()">
|
||||
<li v-if="!group.hidden" class="list-group-item">
|
||||
<p class="h5">
|
||||
<Spelling :text="group.name" />
|
||||
</p>
|
||||
<p v-if="group.description" class="small my-1">
|
||||
<Icon v="info-circle" />
|
||||
<LinkedText :text="group.description" />
|
||||
</p>
|
||||
<SimplePronounList :pronouns="groupPronouns" />
|
||||
</li>
|
||||
</template>
|
||||
<li v-if="config.pronouns.generator.enabled" id="generator" class="list-group-item">
|
||||
<p class="h5">
|
||||
<T>home.generator.header</T>
|
||||
</p>
|
||||
<p>
|
||||
<T>home.generator.description</T>
|
||||
</p>
|
||||
<a v-if="!customise" href="#" class="btn btn-outline-primary w-100" @click.prevent="customise = true">
|
||||
<Icon v="sliders-h-square" />
|
||||
<T>home.generator.button</T>
|
||||
</a>
|
||||
<div v-else class="card mb-5">
|
||||
<div class="card-header">
|
||||
<Icon v="sliders-h-square" />
|
||||
<T>home.generator.header2</T>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-title border-bottom pb-3">
|
||||
<p><strong><T>home.generator.base</T><T>quotation.colon</T></strong></p>
|
||||
<ul class="list-unstyled">
|
||||
<template v-for="[group, groupPronouns] in pronounLibrary.split()">
|
||||
<li v-if="!group.hidden">
|
||||
<ul class="list-inline">
|
||||
<li class="list-inline-item">
|
||||
<Spelling :text="group.name" />
|
||||
</li>
|
||||
<template v-for="pronoun in deduplicatePronounGroup(groupPronouns)">
|
||||
<li v-if="!pronoun.hidden" class="list-inline-item">
|
||||
<button
|
||||
:class="['btn', pronoun.name(glue) === selectedPronoun.name(glue) ? 'btn-primary' : 'btn-outline-primary', 'btn-sm', 'my-1']"
|
||||
@click="selectedPronoun = pronoun.clone(true)"
|
||||
>
|
||||
<Spelling :text="pronoun.name(glue)" />
|
||||
</button>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="alert alert-primary">
|
||||
<p class="h3 mb-0 text-center">
|
||||
<Spelling escape :text="selectedPronoun.name(glue)" />
|
||||
<template v-if="config.pronouns.generator.description ?? true">
|
||||
<br>
|
||||
<input
|
||||
v-model="selectedPronoun.description"
|
||||
class="form-control form-input p-0 form-control-sm"
|
||||
:size="selectedPronoun.description.length ? selectedPronoun.description.length + 3 : 16"
|
||||
:maxlength="DESCRIPTION_MAXLENGTH"
|
||||
:placeholder="$t('profile.description')"
|
||||
>
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
<T>pronouns.examples.header</T><T>quotation.colon</T>
|
||||
</p>
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
</p>
|
||||
<!-- TODO #136
|
||||
<p class="small" v-if="config.pronunciation.enabled && $te('home.generator.pronunciation')">
|
||||
<Icon v="info-circle"/>
|
||||
<T>home.generator.pronunciation</T>
|
||||
</p>
|
||||
-->
|
||||
</div>
|
||||
<div v-if="link" class="card-footer">
|
||||
<LinkInput :link="link" />
|
||||
<div
|
||||
v-if="!usedBaseEquals && (config.pronouns.generator.disclaimer ?? true)"
|
||||
class="alert alert-warning"
|
||||
>
|
||||
<Icon v="exclamation-triangle" />
|
||||
<T>pronouns.generated</T>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li id="multiple" class="list-group-item">
|
||||
<p class="h5">
|
||||
<Spelling :text="config.pronouns.multiple.name" />
|
||||
</p>
|
||||
<p v-if="config.pronouns.multiple.description" class="small my-1">
|
||||
<Icon v="info-circle" />
|
||||
<LinkedText :text="config.pronouns.multiple.description" />
|
||||
</p>
|
||||
<SimplePronounList :pronouns="config.pronouns.multiple.examples" class="mb-3" />
|
||||
<a v-if="!customiseMultiple" href="#" class="btn btn-outline-primary w-100" @click.prevent="customiseMultiple = true">
|
||||
<Icon v="sliders-h-square" />
|
||||
<T>pronouns.alt.button</T>
|
||||
</a>
|
||||
<div v-else class="card">
|
||||
<div class="card-header">
|
||||
<Icon v="sliders-h-square" />
|
||||
<T>pronouns.alt.button</T><T>quotation.colon</T>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
<ul class="list-inline d-inline mb-0">
|
||||
<template v-for="(pronoun, pronounName) in pronouns">
|
||||
<li v-if="!pronoun.hidden" class="list-inline-item">
|
||||
<button
|
||||
:class="['btn', multiple.includes(pronounName) ? 'btn-primary' : 'btn-outline-primary', 'btn-sm', 'my-1']"
|
||||
@click="toggleMultiple(pronounName)"
|
||||
>
|
||||
<Spelling :text="pronoun.name('')" />
|
||||
</button>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="linkMultiple" class="card-footer">
|
||||
<LinkInput :link="linkMultiple" />
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li v-if="config.pronouns.null !== false" id="nameself" class="list-group-item">
|
||||
<p class="h5">
|
||||
<LinkedText :text="config.pronouns.null.description" />
|
||||
<NormativeBadge />
|
||||
</p>
|
||||
<p v-if="config.pronouns.null.history" class="small my-1">
|
||||
<Icon v="info-circle" />
|
||||
<LinkedText :text="config.pronouns.null.history" />
|
||||
</p>
|
||||
<SimplePronounList :pronouns="config.pronouns.null.examples ?? []" class="mb-3" />
|
||||
<button
|
||||
v-if="!customiseNullPronouns"
|
||||
type="button"
|
||||
class="btn btn-outline-primary w-100"
|
||||
@click.prevent="customiseNullPronouns = true"
|
||||
>
|
||||
<Icon v="sliders-h-square" />
|
||||
<T>pronouns.null.button</T>
|
||||
</button>
|
||||
<div v-else class="card">
|
||||
<div class="card-header">
|
||||
<Icon v="sliders-h-square" />
|
||||
<T>pronouns.null.button</T><T>quotation.colon</T>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label for="nullPronounsBase"><T>pronouns.null.base</T></label>
|
||||
<input
|
||||
id="nullPronounsBase"
|
||||
v-model="nullPronounsBase"
|
||||
class="form-control"
|
||||
:maxlength="NULL_PRONOUNS_MAXLENGTH"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="nullPronounsLink" class="card-footer">
|
||||
<LinkInput :link="nullPronounsLink" />
|
||||
<div v-if="config.pronouns.generator.disclaimer ?? true" class="alert alert-warning">
|
||||
<Icon v="exclamation-triangle" />
|
||||
<T>pronouns.generated</T>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li v-if="config.pronouns.emoji !== false" class="list-group-item">
|
||||
<p class="h5">
|
||||
<Spelling :text="config.pronouns.emoji.description" />
|
||||
</p>
|
||||
<div v-if="config.pronouns.emoji.history" class="small my-1">
|
||||
<Icon v="info-circle" />
|
||||
<LinkedText :text="config.pronouns.emoji.history" />
|
||||
</div>
|
||||
<SimplePronounList :pronouns="config.pronouns.emoji.examples" class="mb-3" />
|
||||
<button
|
||||
v-if="!customiseEmojiPronouns"
|
||||
type="button"
|
||||
class="btn btn-outline-primary w-100"
|
||||
@click.prevent="customiseEmojiPronouns = true"
|
||||
>
|
||||
<Icon v="sliders-h-square" />
|
||||
<T>pronouns.emoji.button</T>
|
||||
</button>
|
||||
<div v-else class="card">
|
||||
<div class="card-header">
|
||||
<Icon v="sliders-h-square" />
|
||||
<T>pronouns.emoji.button</T><T>quotation.colon</T>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label for="emojiPronounsBase"><T>pronouns.emoji.base</T></label>
|
||||
<input
|
||||
id="emojiPronounsBase"
|
||||
v-model="emojiPronounsBase"
|
||||
class="form-control"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="emojiPronounsLink" class="card-footer">
|
||||
<LinkInput :link="emojiPronounsLink" />
|
||||
<div v-if="config.pronouns.generator.disclaimer ?? true" class="alert alert-warning">
|
||||
<Icon v="exclamation-triangle" />
|
||||
<T>pronouns.generated</T>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li v-if="config.pronouns.mirror" id="mirror" class="list-group-item">
|
||||
<p class="h5">
|
||||
<nuxt-link :to="{ name: 'pronouns-mirror' }">
|
||||
<LinkedText :text="config.pronouns.mirror.name" />
|
||||
</nuxt-link>
|
||||
</p>
|
||||
<p v-if="config.pronouns.mirror.description" class="small my-1">
|
||||
<Icon v="info-circle" />
|
||||
<LinkedText :text="config.pronouns.mirror.description" />
|
||||
</p>
|
||||
</li>
|
||||
<li v-if="config.pronouns.any" class="list-group-item">
|
||||
<p class="h5">
|
||||
<nuxt-link :to="{ name: 'pronouns-any' }">
|
||||
<T>pronouns.any.header</T>
|
||||
</nuxt-link>
|
||||
</p>
|
||||
<p class="small my-1">
|
||||
<Icon v="info-circle" />
|
||||
<T>pronouns.any.description</T>
|
||||
</p>
|
||||
<ul v-if="Object.keys(pronounLibrary.byKey()).length" class="small">
|
||||
<li>
|
||||
<nuxt-link :to="{ name: 'pronouns-any' }">
|
||||
<T>pronouns.any.short</T>
|
||||
</nuxt-link>
|
||||
</li>
|
||||
<li v-for="(merged, key) in pronounLibrary.byKey()" :key="key">
|
||||
<nuxt-link :to="{ name: 'pronouns-any-group', params: { group: key } }">
|
||||
<Spelling :text="merged.short($translator)" />
|
||||
</nuxt-link>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li v-if="config.pronouns.ask" class="list-group-item">
|
||||
<p class="h5">
|
||||
<template v-for="(variant, i) in config.pronouns.ask.routes" :key="variant">
|
||||
{{ i === 0 ? '' : ' / ' }}
|
||||
<nuxt-link :to="{ name: `pronouns-${variant}` }">
|
||||
<T>pronouns.ask.header.{{ variant }}</T>
|
||||
</nuxt-link>
|
||||
</template>
|
||||
</p>
|
||||
<p class="small my-1">
|
||||
<Icon v="info-circle" />
|
||||
<T>pronouns.ask.description</T>
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
<PronounsIndex examples />
|
||||
</section>
|
||||
|
||||
<AdPlaceholder :phkey="['content-1', 'content-mobile-1']" />
|
||||
@ -349,171 +49,6 @@
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { useNuxtApp } from 'nuxt/app';
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { examples, pronouns, pronounLibrary } from '~/src/data.ts';
|
||||
import { NULL_PRONOUNS_MAXLENGTH } from '~/src/buildPronoun.ts';
|
||||
import { ExampleCategory, ExamplePart, Pronoun } from '~/src/classes.ts';
|
||||
import { isEmoji } from '~/src/helpers.ts';
|
||||
import Compressor from '~/src/compressor.ts';
|
||||
import MORPHEMES from '~/data/pronouns/morphemes.ts';
|
||||
import { mapState } from 'pinia';
|
||||
import Suggested from '~/data/pronouns/Suggested.vue';
|
||||
import useConfig from '~/composables/useConfig.ts';
|
||||
import { useMainStore } from '~/store/index.ts';
|
||||
|
||||
export default defineComponent({
|
||||
components: { Suggested },
|
||||
setup() {
|
||||
definePageMeta({
|
||||
translatedPaths: (config) => translatedPathByConfigModule(config.pronouns),
|
||||
});
|
||||
|
||||
const { $translator: translator } = useNuxtApp();
|
||||
const config = useConfig();
|
||||
|
||||
if (!config.pronouns.enabled) {
|
||||
throw null;
|
||||
}
|
||||
|
||||
const exampleCategories = ExampleCategory.from(examples, 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 {
|
||||
config,
|
||||
|
||||
examplesByHonorific,
|
||||
pronouns,
|
||||
pronounLibrary,
|
||||
|
||||
selectedPronoun: ref(pronouns[config.pronouns.default].clone(true)),
|
||||
selectedMorpheme: ref(''),
|
||||
|
||||
customiseMultiple: ref(false),
|
||||
multiple: ref(config.pronouns.multiple ? config.pronouns.multiple.examples[0].split('&') : []),
|
||||
|
||||
customise: ref(config.pronouns.generator.autoOpen ?? false),
|
||||
|
||||
customiseNullPronouns: ref(false),
|
||||
nullPronounsBase: ref(''),
|
||||
|
||||
customiseEmojiPronouns: false,
|
||||
emojiPronounsBase: ref(''),
|
||||
|
||||
glue: ` ${translator.translate('pronouns.or')} `,
|
||||
|
||||
DESCRIPTION_MAXLENGTH: Pronoun.DESCRIPTION_MAXLENGTH,
|
||||
NULL_PRONOUNS_MAXLENGTH,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(useMainStore, [
|
||||
'user',
|
||||
]),
|
||||
usedBase(): string | null {
|
||||
const name = this.selectedPronoun.name(this.glue);
|
||||
for (const key in this.pronouns) {
|
||||
if (this.pronouns.hasOwnProperty(key)) {
|
||||
if (key === name) {
|
||||
return key;
|
||||
}
|
||||
for (const alias of this.pronouns[key].aliases) {
|
||||
if (alias === name) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
usedBaseEquals(): boolean {
|
||||
return !!this.usedBase && this.selectedPronoun.equals(this.pronouns[this.usedBase], true);
|
||||
},
|
||||
longLink(): string {
|
||||
const base = this.pronouns[this.selectedPronoun.morphemes[MORPHEMES[0]]!];
|
||||
|
||||
return base
|
||||
? Compressor.compress(
|
||||
this.selectedPronoun.toArray().map((x) => x.split('|')[0]),
|
||||
base.toArray().map((x) => x.split('|')[0]),
|
||||
).join(',')
|
||||
: this.selectedPronoun.toString();
|
||||
},
|
||||
link(): string | null {
|
||||
if (!this.selectedPronoun.pronoun()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const slashes = this.selectedPronoun.toStringSlashes(this.$translator);
|
||||
|
||||
let link;
|
||||
if (this.usedBaseEquals) {
|
||||
link = this.usedBase;
|
||||
} else if (slashes) {
|
||||
link = slashes;
|
||||
} else {
|
||||
link = this.longLink;
|
||||
}
|
||||
|
||||
return this.addSlash(`${this.$config.public.baseUrl + (this.config.pronouns.prefix || '')}/${link}`);
|
||||
},
|
||||
linkMultiple(): string | null {
|
||||
if (!this.multiple.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.addSlash(`${this.$config.public.baseUrl + (this.config.pronouns.prefix || '')}/${this.multiple.join('&')}`);
|
||||
},
|
||||
nullPronounsLink(): string | null {
|
||||
if (!this.nullPronounsBase) {
|
||||
return null;
|
||||
}
|
||||
return `${this.$config.public.baseUrl + (this.config.pronouns.prefix || '')}/:${this.nullPronounsBase}`;
|
||||
},
|
||||
emojiPronounsLink(): string | null {
|
||||
if (!isEmoji(this.emojiPronounsBase)) {
|
||||
return null;
|
||||
}
|
||||
return `${this.$config.public.baseUrl + (this.config.pronouns.prefix || '')}/${this.emojiPronounsBase}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleMultiple(name: string): void {
|
||||
const index = this.multiple.indexOf(name);
|
||||
if (index > -1) {
|
||||
this.multiple.splice(index, 1);
|
||||
} else {
|
||||
this.multiple.push(name);
|
||||
}
|
||||
},
|
||||
addSlash(link: string): string {
|
||||
return link + (['*', '\''].includes(link.substr(link.length - 1)) ? '/' : '');
|
||||
},
|
||||
clearExampleParts(parts: ExamplePart[]): ExamplePart[] {
|
||||
return parts.map((p) => new ExamplePart(p.variable, p.str.replace(/^'/, '')));
|
||||
},
|
||||
deduplicatePronounGroup(pronounGroup: Pronoun[]): Pronoun[] {
|
||||
const dict: Record<string, Pronoun> = {};
|
||||
for (const pronoun of pronounGroup) {
|
||||
if (dict.hasOwnProperty(pronoun.name(this.glue))) {
|
||||
continue;
|
||||
}
|
||||
dict[pronoun.name(this.glue)] = pronoun;
|
||||
}
|
||||
return Object.values(dict);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../assets/variables";
|
||||
|
||||
|
@ -1,6 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import { clearLinkedText } from '~/src/helpers.ts';
|
||||
|
||||
definePageMeta({
|
||||
translatedPaths: (config) => {
|
||||
if (!config.pronouns.enabled || !config.pronouns.mirror) {
|
||||
@ -18,7 +16,7 @@ if (!config.pronouns.enabled || !config.pronouns.mirror) {
|
||||
const { $translator: translator } = useNuxtApp();
|
||||
const route = useRoute();
|
||||
useSimpleHead({
|
||||
title: config.pronouns.mirror.name,
|
||||
title: `${translator.translate('pronouns.intro')}: ${config.pronouns.mirror.name}`,
|
||||
description: config.pronouns.mirror.description,
|
||||
banner: `api/banner${route.path.replace(/\/$/, '')}.png`,
|
||||
}, translator);
|
||||
@ -27,19 +25,51 @@ useSimpleHead({
|
||||
<template>
|
||||
<Page v-if="config.pronouns.enabled && config.pronouns.mirror">
|
||||
<h2>
|
||||
{{ clearLinkedText(config.pronouns.mirror.name) }}
|
||||
<Icon v="tag" />
|
||||
<T>pronouns.intro</T><T>quotation.colon</T>
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
<LinkedText :text="config.pronouns.mirror.description" />
|
||||
</p>
|
||||
<section>
|
||||
<div class="alert alert-primary">
|
||||
<h2 class="text-center mb-0">
|
||||
<strong><Spelling :text="config.pronouns.mirror.name" /></strong>
|
||||
</h2>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ul>
|
||||
<li v-for="(example, i) in config.pronouns.mirror.example" :key="i">
|
||||
<LinkedText :text="example" />
|
||||
</li>
|
||||
</ul>
|
||||
<section>
|
||||
<h2 class="h4">
|
||||
<Icon v="file-signature" />
|
||||
<T>pronouns.examples.header</T><T>quotation.colon</T>
|
||||
</h2>
|
||||
<ul>
|
||||
<li v-for="(example, i) in config.pronouns.mirror.example" :key="i">
|
||||
<LinkedText :text="example" />
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="alert alert-light">
|
||||
<Icon v="info-circle" />
|
||||
<LinkedText :text="config.pronouns.mirror.description" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<AdPlaceholder :phkey="['content-0', 'content-mobile-0']" />
|
||||
|
||||
<section>
|
||||
<Share :title="`${$t('pronouns.intro')}: ${config.pronouns.mirror.name}`" />
|
||||
</section>
|
||||
|
||||
<Separator icon="info" />
|
||||
<section class="mb-0">
|
||||
<h2 class="h4">
|
||||
<Icon v="info-circle" />
|
||||
<T>home.whatisit</T>
|
||||
</h2>
|
||||
<T>home.about</T>
|
||||
<Homepage align="center" />
|
||||
</section>
|
||||
</Page>
|
||||
</template>
|
||||
|
@ -1,3 +1,111 @@
|
||||
<script setup lang="ts">
|
||||
import { examples, pronouns, pronounLibrary } from '~/src/data.ts';
|
||||
import { buildPronoun } from '~/src/buildPronoun.ts';
|
||||
import { useNuxtApp } from 'nuxt/app';
|
||||
import useSimpleHead from '~/composables/useSimpleHead.ts';
|
||||
import GrammarTables from '~/data/pronouns/GrammarTables.vue';
|
||||
import LinkedText from '~/components/LinkedText.vue';
|
||||
import { ExampleCategory, SourceLibrary } from '~/src/classes.ts';
|
||||
import useConfig from '~/composables/useConfig.ts';
|
||||
|
||||
definePageMeta({
|
||||
name: 'all',
|
||||
path: '/:path(.*)',
|
||||
});
|
||||
|
||||
const { $translator: translator } = useNuxtApp();
|
||||
const route = useRoute();
|
||||
const config = useConfig();
|
||||
|
||||
const key = computed(() => {
|
||||
let url = route.path;
|
||||
if (config.pronouns.prefix) {
|
||||
if (!url.startsWith(config.pronouns.prefix)) {
|
||||
return null;
|
||||
}
|
||||
url = url.substring(config.pronouns.prefix.length);
|
||||
}
|
||||
return decodeURIComponent(url.substr(1).replace(/\/$/, ''));
|
||||
});
|
||||
|
||||
const selectedPronoun = config.pronouns.enabled && key
|
||||
? buildPronoun(pronouns, key.value, config, translator)
|
||||
: null;
|
||||
|
||||
const glue = ` ${translator.translate('pronouns.or')} `;
|
||||
|
||||
if (selectedPronoun) {
|
||||
useSimpleHead({
|
||||
title: `${translator.translate('pronouns.intro')}: ${selectedPronoun.name(glue)}`,
|
||||
description: [
|
||||
translator.translate('pronouns.examples.header', {}, false),
|
||||
translator.translate('pronouns.grammarTable', {}, false),
|
||||
translator.translate('sources.headerLong', {}, false),
|
||||
].filter((x) => !!x).join(', '),
|
||||
banner: `api/banner${route.path.replace(/\/$/, '')}.png`,
|
||||
}, translator);
|
||||
}
|
||||
|
||||
const { data: sources } = useFetch('/api/sources', { lazy: true });
|
||||
|
||||
const isNull = key.value?.startsWith(':');
|
||||
|
||||
const exampleCategories = ExampleCategory.from(examples, config);
|
||||
const nameOptions = selectedPronoun ? selectedPronoun.nameOptions() : [];
|
||||
const pronounGroup = pronounLibrary.find(selectedPronoun);
|
||||
|
||||
const comprehensive = useComprehensive();
|
||||
|
||||
const sourceLibrary = computed(() => {
|
||||
if (sources.value === null) {
|
||||
return null;
|
||||
}
|
||||
return new SourceLibrary(config, sources.value);
|
||||
});
|
||||
const groupedSources = computed(() => {
|
||||
if (!selectedPronoun || sourceLibrary.value === null || Object.hasOwn(route.query, 'nosources')) {
|
||||
return {};
|
||||
}
|
||||
|
||||
let key = selectedPronoun.canonicalName;
|
||||
if (config.sources.enabled && config.sources.mergePronouns[key] !== undefined) {
|
||||
key = config.sources.mergePronouns[key];
|
||||
}
|
||||
return sourceLibrary.value.getForPronounExtended(key);
|
||||
});
|
||||
|
||||
const addSlash = (link: string) => {
|
||||
return link + (['*', '\''].includes(link.substr(link.length - 1)) ? '/' : '');
|
||||
};
|
||||
|
||||
const counter = ref(0);
|
||||
const counterHandle = ref<number | undefined>();
|
||||
const counterSpeed = ref(3000);
|
||||
|
||||
const setCounterInterval = () => {
|
||||
if (counterHandle.value) {
|
||||
clearInterval(counterHandle.value);
|
||||
}
|
||||
if (counterSpeed.value > 0) {
|
||||
counter.value++;
|
||||
counterHandle.value = setInterval((_) => counter.value++, counterSpeed.value);
|
||||
}
|
||||
};
|
||||
|
||||
watch(counterSpeed, () => setCounterInterval());
|
||||
onMounted(() => setCounterInterval());
|
||||
|
||||
const counterPause = () => {
|
||||
counterSpeed.value = 0;
|
||||
};
|
||||
const counterSlow = () => {
|
||||
counterSpeed.value = 3000;
|
||||
};
|
||||
const counterFast = () => {
|
||||
counterSpeed.value = 1000;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page>
|
||||
<NotFound v-if="!selectedPronoun" />
|
||||
@ -28,7 +136,7 @@
|
||||
<div class="alert alert-primary">
|
||||
<h2 class="text-center mb-0">
|
||||
<template v-if="nameOptions.length === 1">
|
||||
<strong><Spelling escape :text="selectedPronoun.name(glue)" /></strong><small v-if="selectedPronoun.smallForm">/<Spelling :text="selectedPronoun.morphemes[selectedPronoun.smallForm]" /></small>
|
||||
<strong><Spelling escape :text="selectedPronoun.name(glue)" /></strong><small v-if="selectedPronoun.smallForm">/<Spelling :text="selectedPronoun.morphemes[selectedPronoun.smallForm] ?? ''" /></small>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-for="(nameOption, i) in nameOptions">
|
||||
@ -97,7 +205,11 @@
|
||||
|
||||
<AdPlaceholder :phkey="['content-1', 'content-mobile-1']" />
|
||||
|
||||
<PronounGroup :pronoun-group="pronounGroup" />
|
||||
<PronounGroup
|
||||
v-if="pronounGroup"
|
||||
:pronoun-group="pronounGroup.group"
|
||||
:pronouns="pronounGroup.groupPronouns"
|
||||
/>
|
||||
|
||||
<Avoiding v-if="isNull" />
|
||||
|
||||
@ -123,147 +235,3 @@
|
||||
</div>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { examples, pronouns, pronounLibrary } from '~/src/data.ts';
|
||||
import { buildPronoun } from '~/src/buildPronoun.ts';
|
||||
import { useNuxtApp } from 'nuxt/app';
|
||||
import useSimpleHead from '~/composables/useSimpleHead.ts';
|
||||
import GrammarTables from '~/data/pronouns/GrammarTables.vue';
|
||||
import LinkedText from '~/components/LinkedText.vue';
|
||||
import { ExampleCategory, SourceLibrary } from '~/src/classes.ts';
|
||||
import useConfig from '~/composables/useConfig.ts';
|
||||
|
||||
export default {
|
||||
components: { LinkedText, GrammarTables },
|
||||
setup() {
|
||||
definePageMeta({
|
||||
name: 'all',
|
||||
path: '/:path(.*)',
|
||||
});
|
||||
|
||||
const { $translator: translator } = useNuxtApp();
|
||||
const route = useRoute();
|
||||
const config = useConfig();
|
||||
|
||||
const key = computed(() => {
|
||||
let url = route.path;
|
||||
if (config.pronouns.prefix) {
|
||||
if (!url.startsWith(config.pronouns.prefix)) {
|
||||
return null;
|
||||
}
|
||||
url = url.substring(config.pronouns.prefix.length);
|
||||
}
|
||||
return decodeURIComponent(url.substr(1).replace(/\/$/, ''));
|
||||
});
|
||||
|
||||
const selectedPronoun = config.pronouns.enabled && key
|
||||
? buildPronoun(pronouns, key.value, config, translator)
|
||||
: null;
|
||||
|
||||
const glue = ` ${translator.translate('pronouns.or')} `;
|
||||
|
||||
if (selectedPronoun) {
|
||||
useSimpleHead({
|
||||
title: `${translator.translate('pronouns.intro')}: ${selectedPronoun.name(glue)}`,
|
||||
description: [
|
||||
translator.translate('pronouns.examples.header', {}, false),
|
||||
translator.translate('pronouns.grammarTable', {}, false),
|
||||
translator.translate('sources.headerLong', {}, false),
|
||||
].filter((x) => !!x).join(', '),
|
||||
banner: `api/banner${route.path.replace(/\/$/, '')}.png`,
|
||||
}, translator);
|
||||
}
|
||||
|
||||
const { data: sources } = useFetch('/api/sources', { lazy: true });
|
||||
|
||||
return {
|
||||
config,
|
||||
glue,
|
||||
selectedPronoun,
|
||||
isNull: key.value?.startsWith(':'),
|
||||
sources,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
exampleCategories: ExampleCategory.from(examples, this.config),
|
||||
pronouns,
|
||||
nameOptions: this.selectedPronoun ? this.selectedPronoun.nameOptions() : [],
|
||||
pronounGroup: pronounLibrary.find(this.selectedPronoun),
|
||||
|
||||
counter: 0,
|
||||
counterHandle: null,
|
||||
counterSpeed: 1000,
|
||||
};
|
||||
},
|
||||
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 });
|
||||
},
|
||||
},
|
||||
sourceLibrary() {
|
||||
if (this.sources === null) {
|
||||
return null;
|
||||
}
|
||||
return new SourceLibrary(this.config, this.sources);
|
||||
},
|
||||
groupedSources() {
|
||||
if (this.sourceLibrary === null || this.$route.query.hasOwnProperty('nosources')) {
|
||||
return {};
|
||||
}
|
||||
|
||||
let key = this.selectedPronoun.canonicalName;
|
||||
if (this.config.sources.mergePronouns[key] !== undefined) {
|
||||
key = this.config.sources.mergePronouns[key];
|
||||
}
|
||||
return this.sourceLibrary.getForPronounExtended(key);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (process.client) {
|
||||
this.counterSlow();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addSlash(link) {
|
||||
return link + (['*', '\''].includes(link.substr(link.length - 1)) ? '/' : '');
|
||||
},
|
||||
counterClear() {
|
||||
if (this.counterHandle) {
|
||||
clearInterval(this.counterHandle);
|
||||
}
|
||||
},
|
||||
counterPause() {
|
||||
this.counterSpeed = 0;
|
||||
this.counterClear();
|
||||
},
|
||||
counterSlow() {
|
||||
this.counterSpeed = 3000;
|
||||
this.counterClear();
|
||||
this.counter++;
|
||||
this.counterHandle = setInterval((_) => this.counter++, this.counterSpeed);
|
||||
},
|
||||
counterFast() {
|
||||
this.counterSpeed = 1000;
|
||||
this.counterClear();
|
||||
this.counter++;
|
||||
this.counterHandle = setInterval((_) => this.counter++, this.counterSpeed);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -3,7 +3,7 @@ import type { PronounLibrary, PronounUsage } from './classes.ts';
|
||||
import Compressor from './compressor.ts';
|
||||
import { buildDict, buildList, isEmoji, splitSlashes, unescapeControlSymbols } from './helpers.ts';
|
||||
import MORPHEMES from '../data/pronouns/morphemes.ts';
|
||||
import type { Config, NullPronounsConfig } from '../locale/config.ts';
|
||||
import type { Config } from '../locale/config.ts';
|
||||
import type { Translator } from './translator.ts';
|
||||
import type { PronounData, PronounGroupData } from '../locale/data.ts';
|
||||
|
||||
@ -243,8 +243,13 @@ export const buildPronoun = (
|
||||
|
||||
if (!pronoun && config.pronouns.null && config.pronouns.null.morphemes && path.startsWith(':') &&
|
||||
path.length <= NULL_PRONOUNS_MAXLENGTH + 1) {
|
||||
const template = config.pronouns.null as NullPronounsConfig & { morphemes: Record<string, string> };
|
||||
pronoun = buildPronounFromTemplate(config, path.substring(1), template);
|
||||
pronoun = buildPronounFromTemplate(config, path.substring(1), {
|
||||
description: config.pronouns.null.routes.map((variant) => {
|
||||
return `{/${variant}=${headerForVariant('null', variant, translator)}}`;
|
||||
}).join(' / '),
|
||||
history: translator.translate('pronouns.null.description'),
|
||||
morphemes: config.pronouns.null.morphemes,
|
||||
});
|
||||
}
|
||||
|
||||
if (!pronoun && config.pronouns.generator.slashes !== false) {
|
||||
@ -276,9 +281,7 @@ const buildPronounUsageInternal = (
|
||||
translator: Translator,
|
||||
): PronounUsage | null => {
|
||||
if (config.pronouns.null && config.pronouns.null.routes?.includes(path)) {
|
||||
const specificTranslationKey = `pronouns.null.short.${path}`;
|
||||
const short = translator.has(specificTranslationKey) ? translator.translate(specificTranslationKey) : path;
|
||||
return { short: { options: [short] } };
|
||||
return { short: { options: [shortForVariant('null', path, translator)] } };
|
||||
}
|
||||
if (config.pronouns.mirror && config.pronouns.mirror.route === path) {
|
||||
const specificTranslationKey = 'pronouns.mirror.short';
|
||||
@ -298,8 +301,7 @@ const buildPronounUsageInternal = (
|
||||
}
|
||||
}
|
||||
if (config.pronouns.ask && config.pronouns.ask.routes.includes(path)) {
|
||||
const short = translator.translate(`pronouns.ask.short.${path}`);
|
||||
return { short: { options: [short] } };
|
||||
return { short: { options: [shortForVariant('ask', path, translator)] } };
|
||||
}
|
||||
|
||||
const pronoun = buildPronoun(pronounLibrary.pronouns, path, config, translator);
|
||||
@ -309,6 +311,24 @@ const buildPronounUsageInternal = (
|
||||
return null;
|
||||
};
|
||||
|
||||
export const headerForVariant = (usage: 'null' | 'ask', variant: string, translator: Translator) => {
|
||||
const specificTranslationKey = `pronouns.${usage}.header.${variant}`;
|
||||
return translator.has(specificTranslationKey) ? translator.translate(specificTranslationKey) : variant;
|
||||
};
|
||||
|
||||
export const shortForVariant = (usage: 'null' | 'ask', variant: string, translator: Translator) => {
|
||||
const specificTranslationKey = `pronouns.${usage}.short.${variant}`;
|
||||
return translator.has(specificTranslationKey) ? translator.translate(specificTranslationKey) : variant;
|
||||
};
|
||||
|
||||
export const buildAnyPronounsList = (config: Config, pronounLibrary: PronounLibrary): string[] => {
|
||||
if (!config.pronouns.any) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [config.pronouns.any, ...Object.keys(pronounLibrary.byKey()).map((key) => `${config.pronouns.any}:${key}`)];
|
||||
};
|
||||
|
||||
export const parsePronouns = (
|
||||
config: Config,
|
||||
pronounsRaw: PronounData<string>[],
|
||||
|
@ -453,6 +453,10 @@ export const isValidLink = (url: string | URL): boolean => {
|
||||
}
|
||||
};
|
||||
|
||||
export const addSlash = (link: string): string => {
|
||||
return link + (['*', '\''].includes(link.substring(link.length - 1)) ? '/' : '');
|
||||
};
|
||||
|
||||
export const parseUserJwt = (token: string, publicKey: string, allLocalesUrls: string[]): User | null => {
|
||||
try {
|
||||
const parsed = jwt.verify(token, publicKey, {
|
||||
|
3
src/injectionKeys.ts
Normal file
3
src/injectionKeys.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import type { InjectionKey } from 'vue';
|
||||
|
||||
export const addPronounInjectionKey = Symbol() as InjectionKey<(pronoun: string) => void>;
|
@ -106,6 +106,11 @@ export function listMissingTranslations(
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((!config.pronouns.null || !config.pronouns.null.morphemes) &&
|
||||
keyMatches('pronouns.null.button', 'pronouns.null.base')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.pronouns.emoji && keyMatches('pronouns.emoji.')) {
|
||||
return false;
|
||||
}
|
||||
|
@ -94,8 +94,7 @@ describe('when configured that emojiself pronouns are available', () => {
|
||||
});
|
||||
|
||||
const nullPronounsConfig: NullPronounsConfig = {
|
||||
description: 'Avoiding gendered forms',
|
||||
history: 'Some people prefer not using any pronouns',
|
||||
routes: ['avoiding', 'no-pronouns', 'null', 'pronounless', 'nullpronominal', 'nameself'],
|
||||
morphemes: {
|
||||
pronoun_subject: '#',
|
||||
pronoun_object: '#',
|
||||
@ -109,14 +108,20 @@ const nullPronounsConfig: NullPronounsConfig = {
|
||||
describe('when configured that null pronouns are available', () => {
|
||||
beforeEach(() => {
|
||||
config.pronouns.null = nullPronounsConfig;
|
||||
translations.pronouns.null = {
|
||||
description: 'Some people prefer not using any pronouns',
|
||||
};
|
||||
});
|
||||
|
||||
const expectedDescription = '{/avoiding=avoiding} / {/no-pronouns=no-pronouns} / {/null=null} / ' +
|
||||
'{/pronounless=pronounless} / {/nullpronominal=nullpronominal} / {/nameself=nameself}';
|
||||
|
||||
test('builds pronouns from name', () => {
|
||||
const actual = buildPronoun(pronouns, ':S', config, translator);
|
||||
expect(actual).toEqual(new Pronoun(
|
||||
config,
|
||||
'S',
|
||||
'Avoiding gendered forms',
|
||||
expectedDescription,
|
||||
false,
|
||||
{
|
||||
pronoun_subject: 'S',
|
||||
@ -165,7 +170,7 @@ describe('when configured that null pronouns are available', () => {
|
||||
expect(actual).toEqual(new Pronoun(
|
||||
config,
|
||||
'S',
|
||||
'Avoiding gendered forms',
|
||||
expectedDescription,
|
||||
false,
|
||||
{
|
||||
pronoun_subject: 'S',
|
||||
@ -407,11 +412,7 @@ describe('building the short of a pronoun usage', () => {
|
||||
});
|
||||
describe('of null route without specific translation', () => {
|
||||
beforeEach(() => {
|
||||
config.pronouns.null = {
|
||||
description: nullPronounsConfig.description,
|
||||
history: nullPronounsConfig.history,
|
||||
routes: ['avoiding', 'no-pronouns', 'null', 'pronounless', 'nullpronominal', 'nameself'],
|
||||
};
|
||||
config.pronouns.null = nullPronounsConfig;
|
||||
});
|
||||
|
||||
test('without specific translation', () => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user