import type { Category } from '#shared/classes.ts'; import type { GrammarTablesDefinition } from '#shared/language/grammarTables.ts'; import type { VoiceKey } from '#shared/pronunciation/voices.ts'; import type { LocaleCode } from '~~/locale/locales.ts'; export type Toggable = ({ enabled: true } & T) | { enabled: false } & Partial; export interface Config { /** * language code */ locale: LocaleCode; /** * language code used for formatting * @default locale */ intlLocale?: string; style: StyleConfig; format?: FormatConfig; /** * age limit for users * @default 13 */ ageLimit?: number; header: boolean; /** * disables translation mode * @default false */ disableTranslationProposals?: boolean; pronouns: Toggable; pronunciation?: Toggable; sources: Toggable; nouns: Toggable; community?: CommunityConfig; inclusive: Toggable; terminology: Toggable; names: Toggable; people: Toggable; english: Toggable; faq: Toggable; links: Toggable; contact: Toggable; workshops?: Toggable; support: Toggable; user: Toggable; profile: Toggable; calendar?: Toggable; census: Toggable; blog?: BlogConfig; ads?: Toggable; redirects: RedirectConfig[]; api?: Toggable; /** * Configuration to be set if this locale represents a macrolanguage. A * macrolanguage is a language that has multiple individual languages that * are considered to be variations of the macrolanguage. * * For example, written Norwegian is a macrolanguage that includes Bokmål * and Nynorsk. */ macrolanguage?: MacrolanguageConfig; } /** * Configuration for macrolanguages. A macrolanguage is a language that * has multiple individual languages that are considered to be variations of the * macrolanguage. * * For example, written Norwegian is a macrolanguage that includes Bokmål and * Nynorsk. */ export interface MacrolanguageConfig { /** * Whether the locale is a macrolanguage. */ enabled?: boolean; /** * Which individual languages are part of the macrolanguage. These will be * used to generate the list of individual languages on the language * switcher. */ languages?: IndividualLanguageConfig[]; } /** * Configuration for individual languages that are part of a macrolanguage. */ export interface IndividualLanguageConfig { /** * The language code of the individual language. */ code: LocaleCode; /** * The language codes that should be used for the individual language. This * is used to redirect the user to the correct language when they visit the * macrolanguage's route, based on their browser's Accept-Language header. * If omitted, the user will not be automatically redirected, and will have * to manually select the individual language. * * @example * ```yaml * languages: * - * code: 'nb' * matches: ['nb', 'nob', 'nb-NO'] * - * code: 'nn' * matches: ['nn', 'nno', 'nn-NO'] * ``` */ matches?: string[]; } export type ConfigModule = 'pronouns' | 'pronunciation' | 'sources' | 'nouns' | 'inclusive' | 'terminology' | 'names' | 'people' | 'english' | 'faq' | 'links' | 'contact' | 'workshops' | 'support' | 'user' | 'profile' | 'calendar' | 'census' | 'ads'; export type ConfigWithEnabled = Config & { [K in M]: { enabled: true }; }; export const isEnabled = (config: Config, module: M): config is ConfigWithEnabled => { return !!config[module]?.enabled; }; interface FormatConfig { timezone: string; } interface StyleConfig { /** * text direction * @default "ltr" */ dir?: 'ltr' | 'rtl'; /** * fonts used in headings, sorted by priority */ fontHeadings: string[]; /** * fonts used in texts, sorted by priority */ fontText: string[]; } export interface PronounsConfig { /** * route path for the pronouns list page (translated) */ route: string; /** * prefix to the route path of an individual pronoun (translated) */ prefix?: string; /** * available morpheme identifiers for a pronoun, * typically named after their grammatical function in english snake_case */ morphemes: string[]; /** * grammar table definitions to show morphemes in a compact way */ grammarTables?: GrammarTablesDefinition; /** * route path for any pronoun usage (translated) * `false` means that it is disabled */ any: string | false; /** * when present, enables a switch between a simple view for a pronoun (reduced count of morphemes) * and a comprehensive one (nearly all morphemes). * value describes the query parameter added to the pronoun route path (translated) */ comprehensive?: string; /** * whether pronouns exists which are considered plural and thus alter example sentences */ plurals: boolean; /** * whether pronouns exists which are considered honorifics and thus alter example sentences */ honorifics: boolean; /** * how to group example sentences. if not present, all example sentences are shown */ exampleCategories?: ExampleCategory[]; /** * configuration for generated pronouns */ generator: Toggable; /** * configuration for multiple pronouns */ multiple: MultiplePronounsConfig; /** * whether null pronouns are explained or can be generated. * `false` means that explanation and generation are disabled */ null: NullPronounsConfig | false; /** * whether and how emoji pronouns can be generated. * `false` means that generation is disabled */ emoji: EmojiPronounsConfig | false; /** * whether and how mirror pronouns are explained. * `false` or absent means that explanation is disabled */ mirror?: MirrorPronounsConfig | false; /** * whether asking for pronouns are explained. * `false` or absent means that explanation is disabled */ ask?: AskPronounsConfig | false; /** * group name for pronouns which have no assigned group (translated) */ others?: string; /** * overwrites number of morphemes displayed in the pronoun short * @default 2 */ shortMorphemes?: number; /** * configure subdomains and paths so that the link reads like a sentence */ sentence?: PronounsSentenceConfig; } interface ExampleCategory { /** * short description of the category (translated) */ name: string; /** * which morphemes belong to this category. visible example sentences will contain at least one listed morpheme. * if not present, example categories must be assigned to examples using the comma-separated "categories" column in examples.tsv */ morphemes?: string[]; /** * whether this category is only shown in comprehensive view * @default false */ comprehensive?: boolean; } interface PronounsGeneratorConfig { /** * pronoun which is selected at the beginning. */ startPronoun: string; /** * whether pronouns can be generated via passing morphemes seperated by slashes. * `false` means that generation is disabled. * `true` means that all morphemes of this locale are used in their declared order. * `string[]` means that only the configured morphemes can be used, enabling to reorder and exclude some morphemes. */ slashes: boolean | string[]; /** * whether the generator is opened by default on the pronouns list page * @default false */ autoOpen?: boolean; /** * whether descriptions for generated pronouns are displayed * @default true */ description?: boolean; /** * whether the disclaimer for generated pronouns is displayed * @default true */ disclaimer?: boolean; } interface MultiplePronounsConfig { /** * short description in the header (translated) */ name: string; /** * longer description as text (translated) */ description: string; /** * examples for multiple pronouns, typically multiple base pronouns joined with `&` */ examples: string[]; } export type NullPronounsConfig = { /** * route paths for null pronoun usage (translated). * when empty, disables page * @default [] */ routes: string[]; /** * different strategies of avoiding pronouns */ ideas?: NullPronounsIdea[]; } & ({ /** * when present, defines template for generating a pronoun from a name. use `#` as template character. * when absent, generation is disabled */ morphemes?: Record; /** * examples for null pronouns, typically names or initials prefixed with `:` */ examples: string[]; } | { morphemes?: never; examples?: never; }); interface NullPronounsIdea { /** * short description of a strategy to avoid pronouns (translated) */ header: string; /** * long description as text (translated) */ description?: string; /** * whether this pronoun avoiding strategy is considered to be of normative use * @default false */ normative?: boolean; /** * examples for a pronoun avoiding strategy, the first is a sentence using pronouns, * the second one is the same sentences, but with the pronoun avoiding strategy applied */ examples: [string, string][]; } interface EmojiPronounsConfig { /** * short description in the header (translated) */ description: string; /** * longer description as text (translated) */ history: string; /** * template for generating a pronoun from a name. use `#` as template character */ morphemes: Record; /** * examples for emoji pronouns, each has to be a single emoji character */ examples: string[]; } interface MirrorPronounsConfig { /** * route path for mirror pronoun usage (translated) */ route: string; /** * short description in the header (translated) */ name: string; /** * longer description as text (translated) */ description: string; /** * example for mirror pronoun usage, typically a short story with several persons */ example: string[]; } interface AskPronounsConfig { /** * route paths for ask pronouns usage (translated) */ routes: string[]; } interface PronounsSentenceConfig { /** * subdomains added before the locale domain to begin a sentence, typically a possessive (translated) */ subdomains: string[]; /** * words between the locale domain and a pronoun, typically a verb (translated) */ prefixes: string[]; /** * examples for linking pronouns in a sentence */ examples: string[]; } interface PronunciationConfig { /** * whether to treat letter graphemes as phonemes. useful when the configured locale has no matching voice */ ipa?: boolean; voices: VoiceKey[]; } interface SourcesConfig { /** * route path for sources (translated) */ route: string; /** * whether it is possible that users submit source entries */ submit: boolean; /** * merges pronouns to display the same source for both of them * (key is the alternative pronoun, value is the general pronoun added in the source) */ mergePronouns: Record; /** * additional pronoun descriptors for sources. by default only the canonical names of known pronouns are supported */ extraTypes?: string[]; } interface NounsConfig { /** * route path for nouns (translated) */ route: string; routeMain?: string; /** * route paths for single pages describing an approach in detail (translated) * @default [] */ subroutes?: string[]; hashNamespace?: string; /** * whether the dictionary should be collapsed by default */ collapsable: boolean; /** * whether to have a nonbinary column */ nonbinary?: boolean; /** * whether nouns have plural forms */ plurals: boolean; /** * whether plurals are required when users submit nouns entries */ pluralsRequired: boolean; /** * categories for nouns */ categories: Category[]; /** * whether nouns have different declension depending on case */ declension?: Toggable; /** * whether it is possible that users submit nouns entries */ submit: boolean; /** * whether the configured locale uses noun templates in `nouns/nounTemplates.tsv` */ templates: Toggable; /** * whether the configured locale uses noun conventions in `nouns/nounConventions.suml` */ conventions?: { enabled: boolean }; } interface NounsDeclensionConfig { /** * whether to automatically detect used declension * @default false */ detect?: boolean; } interface NounTemplatesConfig { /** * route path for noun templates (translated) */ route?: string; /** * whether a filter is shown for templates, useful when the language has several templates * @default false */ filter?: boolean; } interface CommunityConfig { /** * route path for community (translated) */ route: string; } interface InclusiveConfig { /** * route path for inclusive terms (translated) */ route: string; hashNamespace?: string; /** * categories for inclusive terms (translated) */ categories: Category[]; } interface TerminologyConfig { /** * route path for terminology (translated) */ route: string; hashNamespace?: string; /** * whether terminology are published for all users. when `false`, only users with the `terms` role can access */ published: boolean; /** * categories for terminology (translated) */ categories: Category[]; } interface NamesConfig { /** * whether names are published for all users. when `false`, only users with the `names` role can access */ published: boolean; /** * route path for names (translated) */ route: string; /** * whether the legally option is available for a submitted name */ legally: boolean; /** * whether name counts are shown */ count: boolean; /** * how to display name counts (key is an abbreviation, value is a icon name) */ countSex: Record; /** * display description of different name parts (key is a number, value is the description) */ countOrdinal: Record; /** * whether namedays can be defined for a submitted name */ namedays: boolean; } interface PeopleConfig { /** * route path for people page (translated) */ route: string; } interface EnglishConfig { /** * route path for english explanation page (typically in english) */ route: string; /** * links to other pages which address the inclusive language of the configured locale in english. * each item is a paragraph * @default [] */ links?: Link[]; /** * description of some inclusive pronoun stragies */ pronounGroups: EnglishPronounGroup[]; } interface EnglishPronounGroup { /** * short description in the header (in english) */ name: string; /** * longer description as text (in english) */ description: string[]; /** * data for the EnglishTable component */ table?: unknown; } interface FaqConfig { /** * route path for faq (translated) */ route: string; } interface LinksConfig { /** * route path for faq (translated) */ route: string; /** * route path for blog (translated) */ blogRoute: string; /** * route path for academic sources (translated) */ academicRoute?: string; /** * route path for media sources (translated) */ mediaRoute?: string; /** * route path for translinguistics (translated) */ translinguisticsRoute?: string; /** * whether blog is enabled */ blog: boolean; /** * @default false */ split?: boolean; splitBlog?: boolean; links: Link[]; academic: Record; mediaGuests: Link[]; mediaMentions: Link[]; recommended: Link[]; names?: Link[]; endorsements?: Record; zine?: Toggable; translinguistics?: AcademicReference[]; } interface LinkCategory { /** * short description of the link category (translated) */ name: string | null; /** * links of this category */ entries: Link[]; } export interface Link { /** * locale codes of the linked resource, e.g. when the source talks in a different locale about the configured locale */ lang?: string[]; /** * icon name * @see https://fontawesome.com/v5/search */ icon: string; /** * icon style (light, solid or brand) * @default "l" */ iconSet?: 'l' | 's' | 'b'; /** * references external resource * @format uri */ url?: string; /** * short description of the resource displayed as header (translated) */ headline?: string; /** * one or multiple quotes from the linked source (language of the resource) * @default [] */ quote?: string | string[]; /** * short description of the resource displayed beside the link (translated) * @default "" */ extra?: string; /** * response to the linked resource */ response?: string | string[]; } interface Endorsement { author: string | null; title: string; publisher: string; /** * @format uri */ link: string; description: string[]; /** * valid blog slug to a review for this publication */ review?: string; /** * dont' show the item before this date (YYYY-MM-DD) */ published?: string; } export interface AcademicReference { /** * year of publication */ year: number; /** * academic source reference (translated) */ reference: string; /** * abstract of the reference. each item is a paragraph (language of the reference) */ abstract: string[]; /** * tags for the reference (translated) */ tags: string[]; /** * languages of the reference when it differs from the configured locale */ lang?: string[]; } interface ZineConfig { /** * route path for zine (translated) */ route: string; /** * whether submissions for zine are open */ open: boolean; releases: ZineRelease[]; } export interface ZineRelease { /** * title text of the zine (translated) */ title: string; /** * references an image of the cover * @format uri */ cover: string; /** * references a horizontal image used as banner * @format uri */ banner: string; /** * long description as text (translated) */ description: string; /** * download options */ downloads: Record; /** * extra information displayed as list */ extra: string[]; } interface ZineDownload { /** * references a downloadable resource * @format uri */ filename: string; /** * icon name * @see https://fontawesome.com/v5/search */ icon: string; } interface ContactConfig { /** * route path for contact (translated) */ route: string; /** * additional authors supplementing the information from the database * @default [] */ authors?: ContactAuthor[]; team: Toggable; } export type ContactAuthor = { footerName: string; username: string; footerAreas: string; } | { footerName: string; /** * @format uri */ link: string; group: true; footerAreas: string; }; interface ContactTeamConfig { /** * route path for team (translated) */ route: string; } interface WorkshopsConfig { /** * route path for workshops (translated) */ route: string; /** * @format email */ email: string; } interface SupportConfig { enabled: boolean; } interface UserConfig { /** * route path for user account (translated) */ route: string; /** * route path for terms of service (translated) */ termsRoute: string; /** * route path for privacy notice (translated) */ privacyRoute: string; } interface ProfileEditorConfig { editorEnabled: boolean; /** * default words for newly created profiles */ defaultWords: ProfileWords[]; /** * default pronoun used for translating declensions in flags.* */ flags?: { defaultPronoun: string }; /** * whether the limit for name lengths is extended * `true` means 255 characters, `false` means 32 characters * @default false */ longNames?: boolean; } type ProfileConfig = ({ editorEnabled: true } & ProfileEditorConfig) | { editorEnabled: false }; export interface ProfileWords { /** * short description of the category in the header (translated) */ header: string | null; /** * default words in this category (translated) */ values: string[]; } interface CalendarConfig { /** * route path for queer calendar (translated) */ route: string; /** * declares used sources for the queer calendar * @format uri * @default [] */ sources?: string[]; } interface CensusConfig { /** * route path for census (translated) */ route: string; /** * key for the census run, typically the year */ edition: string; /** * date when the census opens for submissions * @format date-time */ start: string; /** * date when the census closes for submissions * @format date-time */ end: string; /** * which json file to use for fetching in-page stats */ latestResults: string; /** * which answers for the first question are considered to be relevant for the census */ relevant: string[]; /** * questions of the census */ questions: CensusQuestion[]; /** * result pages of past census (key is a valid blog slug, value is its title) */ results: Record>; } interface CensusBaseQuestion { /** * displayed question (translated) */ question: string; /** * whether this questions only shows depending on the answers of another question * (referenced by its 0-indexed number) */ conditionalOn?: number; /** * which value of the referenced question will show this question */ conditionalValue?: string | string[]; /** * optional instructions. each item is a paragraph (translated) */ instruction?: string[]; /** * whether to add an additional writein text input * @default false */ writein?: boolean; /** * lists options which should only show after the acception of a content warning */ cw?: string[]; /** * Whether answer helpers are shown in a separate line and with formatting (default: false) */ expandedHelp?: boolean; /** * whether the answer to this question is not required * @default false */ optional?: boolean; aggregates?: Record; } export interface CensusOptionsQuestion extends CensusBaseQuestion { type: 'radio' | 'checkbox'; /** * whether to randomise options * @default false */ randomise?: boolean; /** * options always put first when randomising * @default [] */ optionsFirst?: string[][]; options: string[][]; /** * options always put last when randomising * @default [] */ optionsLast?: string[][]; } export interface CensusTextQuestion extends CensusBaseQuestion { type: 'text' | 'textarea'; } export interface CensusNumberQuestion extends CensusBaseQuestion { type: 'number'; min: number; max: number; } type CensusQuestion = CensusOptionsQuestion | CensusTextQuestion | CensusNumberQuestion; export interface Aggregate { operation: 'OR' | 'AND'; values: string[]; exclusive?: boolean; } interface BlogConfig { /** * shortcuts of blog entries directly accessible from the page route * (key is the defined shortcut, value is a valid blog slug) */ shortcuts: Record; /** * keep the full path (with the blog route path) for a blog entry even though a shortcut exists. * items are valid blog slugs * @default [] */ keepFullPath?: string[]; } interface AdsConfig { enabled: boolean; } interface RedirectConfig { from: string; to: string; } interface ApiConfig { /** * route path for api documentation (translated) */ route: string; /** * example routes for the api * (key is a predefined endpoint descriptor, value are several route paths with concrete examples) */ examples: Record; }