diff --git a/packages/inflekt/__tests__/ves-and-exceptions.test.ts b/packages/inflekt/__tests__/ves-and-exceptions.test.ts index 889984d..78aa907 100644 --- a/packages/inflekt/__tests__/ves-and-exceptions.test.ts +++ b/packages/inflekt/__tests__/ves-and-exceptions.test.ts @@ -139,6 +139,57 @@ describe('singularize: suffix classes the inflection library gets wrong', () => }); }); +describe('singularize: -is and -us words', () => { + it('depluralizes coined identifiers whose stem ends in a vowel', () => { + // The regression that started this: an "apis" table became "apises", + // because every -is/-us word was assumed to be already singular. + expect(singularize('apis')).toBe('api'); + expect(singularize('uris')).toBe('uri'); + expect(singularize('guis')).toBe('gui'); + expect(singularize('cpus')).toBe('cpu'); + expect(singularize('gpus')).toBe('gpu'); + expect(singularizeLast('ApiSchemas')).toBe('ApiSchema'); + expect(pluralize(singularize('apis'))).toBe('apis'); + expect(pluralize(singularize('cpus'))).toBe('cpus'); + }); + + it('leaves the dictionary singulars alone', () => { + // "iris" and "apis" have the same shape (a vowel plus "s"), so these are + // dictionary facts, not rules — see SINGULAR_EXCEPTIONS. + for (const word of [ + 'aegis', + 'analysis', + 'anxious', + 'apparatus', + 'bus', + 'cactus', + 'census', + 'chassis', + 'corpus', + 'genius', + 'iris', + 'nucleus', + 'radius', + 'status', + 'tennis', + 'various', + 'virus', + ]) { + expect(singularize(word)).toBe(word); + } + }); + + it('still depluralizes the dictionary plurals of vowel stems', () => { + expect(singularize('menus')).toBe('menu'); + expect(singularize('emus')).toBe('emu'); + expect(singularize('skis')).toBe('ski'); + expect(singularize('taxis')).toBe('taxi'); + expect(singularize('alibis')).toBe('alibi'); + expect(singularize('statuses')).toBe('status'); + expect(singularize('irises')).toBe('iris'); + }); +}); + describe('SINGULAR_EXCEPTIONS table', () => { it('is applied for every entry', () => { for (const [plural, singular] of Object.entries(SINGULAR_EXCEPTIONS)) { diff --git a/packages/inflekt/scripts/generate-exceptions.ts b/packages/inflekt/scripts/generate-exceptions.ts index dc3656f..3ddfcca 100644 --- a/packages/inflekt/scripts/generate-exceptions.ts +++ b/packages/inflekt/scripts/generate-exceptions.ts @@ -179,6 +179,16 @@ for (const singular of [...exceptions.values()]) { } } +// Round-trip closure for the singulars that end in "s" (aegis, iris, corpus): +// their plural takes "-es", and the generic rules read that back one letter +// short (aegises -> aegise), because "-ses" is only "-sis" for the Greek nouns. +for (const singular of [...new Set(exceptions.values())]) { + if (!singular.endsWith('s')) continue; + const plural = pluralize(singular); + if (plural === singular || exceptions.has(plural)) continue; + if (singularizeByRules(plural) !== singular) exceptions.set(plural, singular); +} + const entries = [...exceptions.entries()].sort(([a], [b]) => a.localeCompare(b)); const body = entries.map(([plural, singular]) => ` ${plural}: '${singular}',`).join('\n'); diff --git a/packages/inflekt/src/exceptions.ts b/packages/inflekt/src/exceptions.ts index f7ce4f9..e1247da 100644 --- a/packages/inflekt/src/exceptions.ts +++ b/packages/inflekt/src/exceptions.ts @@ -16,46 +16,54 @@ * pluralization returns the key (see scripts/generate-exceptions.ts). */ export const SINGULAR_EXCEPTIONS: Record = { - abacis: 'abaci', + abacus: 'abacus', abases: 'abase', abstruses: 'abstruse', abuses: 'abuse', - acanthis: 'acanthi', + acanthus: 'acanthus', accouterments: 'accouterments', accoutrements: 'accoutrements', accuses: 'accuse', aches: 'ache', - adieus: 'adieu', ads: 'ads', + aegis: 'aegis', + aegises: 'aegis', aerobatics: 'aerobatics', aeronautics: 'aeronautics', afterlives: 'afterlife', airwaves: 'airwaves', alas: 'alas', - alibis: 'alibi', alkalies: 'alkali', - alkalis: 'alkali', alms: 'alms', aloes: 'aloe', - alumnis: 'alumni', + alumnus: 'alumnus', always: 'always', amanuenses: 'amanuensis', + amaryllis: 'amaryllis', amaryllises: 'amaryllis', + ambergris: 'ambergris', + ambergrises: 'ambergris', amidships: 'amidships', amniocenteses: 'amniocentesis', amuses: 'amuse', - anis: 'ani', + animus: 'animus', annals: 'annals', anopheles: 'anopheles', - antipastis: 'antipasti', antipodes: 'antipodes', - antis: 'anti', + anus: 'anus', apices: 'apex', apotheoses: 'apotheosis', + apparatus: 'apparatus', appendices: 'appendix', + appendicitis: 'appendicitis', + appendicitises: 'appendicitis', apropos: 'apropos', + arbutus: 'arbutus', arrears: 'arrears', + arthritis: 'arthritis', + arthritises: 'arthritis', asbestos: 'asbestos', + asparagus: 'asparagus', astronautics: 'astronautics', astrophysics: 'astrophysics', atlas: 'atlas', @@ -63,113 +71,122 @@ export const SINGULAR_EXCEPTIONS: Record = { avalanches: 'avalanche', aviatrices: 'aviatrix', avionics: 'avionics', + avoirdupois: 'avoirdupois', + avoirdupoises: 'avoirdupois', axes: 'axis', - bacillis: 'bacilli', + bacillus: 'bacillus', backaches: 'backache', backhoes: 'backhoe', backstairs: 'backstairs', backwoods: 'backwoods', badlands: 'badlands', - bandittis: 'banditti', - banis: 'bani', banns: 'banns', bassis: 'bassi', bathos: 'bathos', bayous: 'bayou', - beaus: 'beau', bedclothes: 'bedclothes', beeves: 'beef', behalves: 'behalf', belies: 'belie', bellyaches: 'bellyache', bemuses: 'bemuse', - beriberis: 'beriberi', bias: 'bias', biases: 'bias', biceps: 'biceps', bicepses: 'biceps', biggies: 'biggie', - bikinis: 'bikini', billiards: 'billiards', biophysics: 'biophysics', birdies: 'birdie', blice: 'blouse', blintzes: 'blintze', + bogus: 'bogus', bonkers: 'bonkers', bonnies: 'bonnie', - bonsais: 'bonsai', boogies: 'boogie', bookies: 'bookie', bookshelves: 'bookshelf', boondocks: 'boondocks', bouillabaisses: 'bouillabaisse', + bourgeois: 'bourgeois', + bourgeoises: 'bourgeois', bourgeoisies: 'bourgeoisie', breakpoints: 'breakpoints', britches: 'britches', - broccolis: 'broccoli', - bronchis: 'bronchi', - brontosauris: 'brontosauri', + bronchitis: 'bronchitis', + bronchitises: 'bronchitis', + bronchus: 'bronchus', + brontosaurus: 'brontosaurus', brownies: 'brownie', budgies: 'budgie', - bureaus: 'bureau', burnouses: 'burnous', + bursitis: 'bursitis', + bursitises: 'bursitis', butterfingers: 'butterfingers', caches: 'cache', - cactis: 'cacti', - caduceis: 'caducei', - calculis: 'calculi', + cactus: 'cactus', + caduceus: 'caduceus', + calculus: 'calculus', callisthenics: 'callisthenics', callouses: 'callous', + callus: 'callus', calories: 'calorie', calves: 'calf', calyces: 'calyces', camaraderies: 'camaraderie', - campanilis: 'campanili', + cannabis: 'cannabis', cannabises: 'cannabis', canoes: 'canoe', carbs: 'carbs', caribous: 'caribou', caries: 'caries', - carpis: 'carpi', + carpus: 'carpus', caryatides: 'caryatid', catharses: 'catharsis', - cellis: 'celli', + caucus: 'caucus', + census: 'census', cervices: 'cervix', - chichis: 'chichi', + chamois: 'chamois', + chamoises: 'chamois', + chateaus: 'chateaus', chilies: 'chili', - chilis: 'chili', - chillis: 'chilli', - chis: 'chi', chitlings: 'chitlings', chitlins: 'chitlins', chitterlings: 'chitterlings', + chorus: 'chorus', chrysalides: 'chrysalides', + chrysalis: 'chrysalis', chrysalises: 'chrysalis', + circus: 'circus', + cirrus: 'cirrus', + cis: 'cis', + cises: 'cis', + citrus: 'citrus', civies: 'civies', civvies: 'civvies', + clematis: 'clematis', clematises: 'clematis', - cliis: 'clii', + clitoris: 'clitoris', clitorises: 'clitoris', cloches: 'cloche', cloverleaves: 'cloverleaf', - clviis: 'clvii', - clvis: 'clvi', - clxiis: 'clxii', - clxis: 'clxi', - clxviis: 'clxvii', - clxvis: 'clxvi', - coccis: 'cocci', + coccus: 'coccus', coccyges: 'coccyges', cockamamies: 'cockamamie', codices: 'codex', + coitus: 'coitus', + colitis: 'colitis', + colitises: 'colitis', collies: 'collie', colones: 'colon', colossis: 'colossi', - concertis: 'concerti', - confettis: 'confetti', + colossus: 'colossus', confuses: 'confuse', + conjunctivitis: 'conjunctivitis', + conjunctivitises: 'conjunctivitis', conquistadores: 'conquistador', cons: 'cons', + consensus: 'consensus', conses: 'cons', contretemps: 'contretemps', contuses: 'contuse', @@ -177,101 +194,111 @@ export const SINGULAR_EXCEPTIONS: Record = { coolies: 'coolie', cooties: 'cootie', cornflakes: 'cornflakes', + corpus: 'corpus', cortices: 'cortex', cosmos: 'cosmos', cosmoses: 'cosmos', coteries: 'coterie', - crescendis: 'crescendi', crevasses: 'crevasse', - crocis: 'croci', + crocus: 'crocus', crofts: 'crofts', crossbones: 'crossbones', crossways: 'crossways', cryogenics: 'cryogenics', - cumulis: 'cumuli', + cumulus: 'cumulus', + cunnilingus: 'cunnilingus', curies: 'curie', - daiquiris: 'daiquiri', + dais: 'dais', daises: 'dais', - dashikis: 'dashiki', dds: 'dds', deaves: 'deaf', debases: 'debase', + debris: 'debris', + debrises: 'debris', decrements: 'decrements', - decrescendis: 'decrescendi', defuses: 'defuse', - delis: 'deli', deluxes: 'deluxe', demitasses: 'demitasse', + dermatitis: 'dermatitis', + dermatitises: 'dermatitis', + dermis: 'dermis', + dermises: 'dermis', detentes: 'detentes', - dhotis: 'dhoti', + detritus: 'detritus', diabetes: 'diabetes', dialyzes: 'dialyzes', diereses: 'dieresis', dies: 'die', diffuses: 'diffuse', - dilettantis: 'dilettanti', + digitalis: 'digitalis', + digitalises: 'digitalis', + dis: 'dis', disabuses: 'disabuse', + discus: 'discus', + dises: 'dis', disuses: 'disuse', - djinnis: 'djinni', does: 'doe', dogies: 'dogie', doldrums: 'doldrums', dos: 'dos', douches: 'douche', downstairs: 'downstairs', - dpis: 'dpi', - drachmais: 'drachmai', dreadlocks: 'dreadlocks', dregs: 'dregs', drouthes: 'drouth', dwarves: 'dwarf', earaches: 'earache', - ecrus: 'ecru', edgeways: 'edgeways', eds: 'eds', eeries: 'eerie', + egis: 'egis', + egises: 'egis', electrodynamics: 'electrodynamics', ellipses: 'ellipsis', elves: 'elf', - emojis: 'emoji', + emeritus: 'emeritus', emphases: 'emphasis', ems: 'ems', - emus: 'emu', + encephalitis: 'encephalitis', + encephalitises: 'encephalitis', endways: 'endways', - ennuis: 'ennui', enthuses: 'enthuse', entrails: 'entrails', environs: 'environs', + epidermis: 'epidermis', epidermises: 'epidermis', epiglottides: 'epiglottides', + epiglottis: 'epiglottis', epiglottises: 'epiglottis', - esophagis: 'esophagi', + esophagus: 'esophagus', ethos: 'ethos', - eucalyptis: 'eucalypti', + eucalyptus: 'eucalyptus', eugenics: 'eugenics', excuses: 'excuse', executrices: 'executrix', exegeses: 'exegesis', exes: 'exes', + exodus: 'exodus', faeces: 'faeces', fairings: 'fairings', feces: 'feces', + fetus: 'fetus', fezes: 'fez', fezzes: 'fezzes', fiches: 'fiche', fiddlesticks: 'fiddlesticks', fies: 'fie', finesses: 'finesse', + finis: 'finis', finises: 'finis', fishwives: 'fishwife', fisticuffs: 'fisticuffs', flambes: 'flambes', floes: 'floe', flyleaves: 'flyleaf', - focis: 'foci', focusses: 'focusses', foes: 'foe', - fondus: 'fondu', + foetus: 'foetus', footlights: 'footlights', footsies: 'footsie', forceps: 'forceps', @@ -281,33 +308,35 @@ export const SINGULAR_EXCEPTIONS: Record = { freebases: 'freebase', freebies: 'freebie', ftpers: 'ftpers', - fungis: 'fungi', + fungus: 'fungus', fuses: 'fuse', gallowses: 'gallows', gas: 'gas', gases: 'gas', gasses: 'gasses', + gastritis: 'gastritis', + gastritises: 'gastritis', gasworks: 'gasworks', gauches: 'gauche', geneses: 'genesis', genies: 'genie', - geniis: 'genii', + genius: 'genius', geniuses: 'genius', geocaches: 'geocache', geophysics: 'geophysics', geopolitics: 'geopolitics', - gladiolis: 'gladioli', - glissandis: 'glissandi', + gingivitis: 'gingivitis', + gingivitises: 'gingivitis', + gladiolus: 'gladiolus', glottides: 'glottides', + glottis: 'glottis', glottises: 'glottis', - gnus: 'gnu', goalies: 'goalie', goes: 'goes', - graffitis: 'graffiti', + gratis: 'gratis', + gratises: 'gratis', groupies: 'groupie', - gurus: 'guru', haemorrhoids: 'haemorrhoids', - haikus: 'haiku', halves: 'half', handkerchieves: 'handkerchief', has: 'has', @@ -316,296 +345,289 @@ export const SINGULAR_EXCEPTIONS: Record = { heartaches: 'heartache', heartstrings: 'heartstrings', helices: 'helix', + hepatitis: 'hepatitis', + hepatitises: 'hepatitis', herpes: 'herpes', herselves: 'herself', - hibachis: 'hibachi', + hiatus: 'hiatus', + hibiscus: 'hibiscus', hies: 'hie', himselves: 'himself', - hippopotamis: 'hippopotami', + hippopotamus: 'hippopotamus', + his: 'his', + hises: 'his', hoes: 'hoe', hoodies: 'hoodie', hooves: 'hoof', horseshoes: 'horseshoe', hos: 'hos', + houmus: 'houmus', housewares: 'housewares', housewives: 'housewife', hrs: 'hrs', - humeris: 'humeri', + hubris: 'hubris', + hubrises: 'hubris', + humerus: 'humerus', + hummus: 'hummus', + humus: 'humus', hustings: 'hustings', hypnoses: 'hypnosis', hypotenuses: 'hypotenuse', - hypothalamis: 'hypothalami', + hypothalamus: 'hypothalamus', ibices: 'ibex', + ibis: 'ibis', ibises: 'ibis', ides: 'ides', ids: 'ids', ifs: 'ifs', - iiis: 'iii', + ignoramus: 'ignoramus', + illus: 'illus', impasses: 'impasse', - impromptus: 'impromptu', - incubis: 'incubi', + impetus: 'impetus', infuses: 'infuse', innards: 'innards', ins: 'ins', - intaglis: 'intagli', - intermezzis: 'intermezzi', intravenouses: 'intravenous', + iris: 'iris', irises: 'iris', isosceles: 'isosceles', - isthmis: 'isthmi', + isthmus: 'isthmus', its: 'its', itselves: 'itself', jackknives: 'jackknife', jaggies: 'jaggies', jalousies: 'jalousie', jeans: 'jeans', - jinnis: 'jinni', jitters: 'jitters', - jiujitsus: 'jiujitsu', jodhpurs: 'jodhpurs', - jujitsus: 'jujitsu', - jujutsus: 'jujutsu', kerchieves: 'kerchief', - khakis: 'khaki', - kiwis: 'kiwi', knickers: 'knickers', knives: 'knife', kohlrabies: 'kohlrabi', - kohlrabis: 'kohlrabi', kookies: 'kookie', kudos: 'kudos', - kudzus: 'kudzu', lacrosses: 'lacrosse', laddies: 'laddie', largesses: 'largesse', larynges: 'larynges', + laryngitis: 'laryngitis', + laryngitises: 'laryngitis', lassies: 'lassie', lazybones: 'lazybones', lbs: 'lbs', leaves: 'leaf', leftwards: 'leftwards', - leis: 'lei', lengthways: 'lengthways', lens: 'lens', lenses: 'lens', levellers: 'levellers', - librettis: 'libretti', lies: 'lie', - lieus: 'lieu', lifeforms: 'lifeforms', - liis: 'lii', lingeries: 'lingerie', - litchis: 'litchi', - literatis: 'literati', + litmus: 'litmus', lives: 'life', loaves: 'loaf', - locis: 'loci', + locus: 'locus', looneyies: 'looneyies', - luaus: 'luau', - lviis: 'lvii', - lvis: 'lvi', - lxiis: 'lxii', - lxis: 'lxi', - lxviis: 'lxvii', - lxvis: 'lxvi', + lotus: 'lotus', + lupus: 'lupus', macaronies: 'macaroni', - macaronis: 'macaroni', madras: 'madras', madrases: 'madras', - maestris: 'maestri', magpies: 'magpie', - maharanis: 'maharani', - maharishis: 'maharishi', mantes: 'mantis', + mantis: 'mantis', mantises: 'mantis', marabous: 'marabou', - mariachis: 'mariachi', - martinis: 'martini', + marquis: 'marquis', + marquises: 'marquis', mas: 'mas', matins: 'matins', meatloaves: 'meatloaf', + megalopolis: 'megalopolis', megalopolises: 'megalopolis', menageries: 'menagerie', + meningitis: 'meningitis', + meningitises: 'meningitis', menservants: 'menservants', menses: 'menses', - menus: 'menu', mes: 'mes', mesdames: 'mesdames', mesdemoiselles: 'mesdemoiselles', messieurs: 'messieurs', - metacarpis: 'metacarpi', + metacarpus: 'metacarpus', metaphysics: 'metaphysics', metastases: 'metastasis', methinks: 'methinks', + metropolis: 'metropolis', metropolises: 'metropolis', microeconomics: 'microeconomics', microfiches: 'microfiche', microfloppies: 'microfloppies', microplastics: 'microplastics', midwives: 'midwife', - milieus: 'milieu', minibusses: 'minibusses', minifloppies: 'minifloppies', - minis: 'mini', miniseries: 'miniseries', mistletoes: 'mistletoe', misuses: 'misuse', + modulus: 'modulus', molasses: 'molasses', monies: 'monies', - monsignoris: 'monsignori', mos: 'mos', mousses: 'mousse', moustaches: 'moustache', - mueslis: 'muesli', - muftis: 'mufti', - multis: 'multi', + mucus: 'mucus', munchies: 'munchies', muses: 'muse', mustaches: 'mustache', - muumuus: 'muumuu', mynahes: 'mynah', myselves: 'myself', naiades: 'naiad', narcissis: 'narcissi', - nautilis: 'nautili', + narcissus: 'narcissus', + nautilus: 'nautilus', neckerchieves: 'neckerchief', neckties: 'necktie', nemeses: 'nemesis', + nephritis: 'nephritis', + nephritises: 'nephritis', + neuritis: 'neuritis', + neuritises: 'neuritis', neuroses: 'neurosis', newbies: 'newbie', newsagents: 'newsagents', + nexus: 'nexus', niches: 'niche', nightclothes: 'nightclothes', nightlives: 'nightlife', - nimbis: 'nimbi', noes: 'noes', + nonplus: 'nonplus', nonplusses: 'nonplusses', nos: 'nos', nowadays: 'nowadays', - nucleis: 'nuclei', + nucleus: 'nucleus', nucleuses: 'nucleus', oases: 'oasis', oboes: 'oboe', obtuses: 'obtuse', - octopis: 'octopi', oftentimes: 'oftentimes', ohs: 'ohs', oldies: 'oldie', omnibusses: 'omnibusses', oneselves: 'oneself', + onus: 'onus', oodles: 'oodles', oops: 'oops', ops: 'ops', + opus: 'opus', organdies: 'organdie', - origamis: 'origami', - ormolus: 'ormolu', ourselves: 'ourselves', overseas: 'overseas', overshoes: 'overshoe', overuses: 'overuse', pajamas: 'pajamas', - palazzis: 'palazzi', pampas: 'pampas', panaches: 'panache', pancreas: 'pancreas', pancreases: 'pancreas', pantaloons: 'pantaloons', - papyris: 'papyri', + papyrus: 'papyrus', paratroops: 'paratroops', - parvenus: 'parvenu', pas: 'pas', pastiches: 'pastiche', - pastramis: 'pastrami', pathos: 'pathos', + patois: 'patois', + patoises: 'patois', pecs: 'pecs', pekoes: 'pekoe', pelves: 'pelvis', + pelvis: 'pelvis', pelvises: 'pelvis', penes: 'penis', + penis: 'penis', penises: 'penis', penknives: 'penknife', - pepperonis: 'pepperoni', perhaps: 'perhaps', periphrases: 'periphrasis', + peritonitis: 'peritonitis', + peritonitises: 'peritonitis', peruses: 'peruse', phalanges: 'phalanges', - phallis: 'phalli', + phallus: 'phallus', pharynges: 'pharynges', - pianissimis: 'pianissimi', + phlebitis: 'phlebitis', + phlebitises: 'phlebitis', + phosphorus: 'phosphorus', piazzes: 'piazze', - piccalillis: 'piccalilli', pies: 'pie', - pilaus: 'pilau', - pizzicatis: 'pizzicati', + pis: 'pis', + pises: 'pis', plainclothes: 'plainclothes', - plateaus: 'plateau', - platypis: 'platypi', + platypus: 'platypus', + plexus: 'plexus', pliers: 'pliers', + plus: 'plus', plusses: 'plusses', pocketknives: 'pocketknife', - pois: 'poi', + poliomyelitis: 'poliomyelitis', + poliomyelitises: 'poliomyelitis', politesses: 'politesse', + portcullis: 'portcullis', portcullises: 'portcullis', - portmanteaus: 'portmanteau', potpies: 'potpie', - potpourris: 'potpourri', prairies: 'prairie', pres: 'pres', proboscides: 'proboscides', + proboscis: 'proboscis', proboscises: 'proboscis', profuses: 'profuse', + prospectus: 'prospectus', psychoses: 'psychosis', + pus: 'pus', quadriceps: 'quadriceps', quadricepses: 'quadriceps', quasis: 'quasi', ques: 'ques', quiches: 'quiche', quickies: 'quickie', + quietus: 'quietus', quizzes: 'quizzes', - rabbis: 'rabbi', rabies: 'rabies', - radiis: 'radii', + radius: 'radius', radiuses: 'radius', - raviolis: 'ravioli', reales: 'real', recluses: 'recluse', + refocus: 'refocus', refocusses: 'refocusses', refuses: 'refuse', - rehis: 'rehi', + reis: 'reis', + reises: 'reis', rendezvouses: 'rendezvous', - rhinoceris: 'rhinoceri', rhinoceros: 'rhinoceros', rhinoceroses: 'rhinoceros', - rhombis: 'rhombi', rickets: 'rickets', roes: 'roe', rookies: 'rookie', rotisseries: 'rotisserie', rps: 'rps', + ruckus: 'ruckus', + rumpus: 'rumpus', ruses: 'ruse', sades: 'sad', - safaris: 'safari', - sakis: 'saki', - salamis: 'salami', - samurais: 'samurai', sans: 'sans', - sarcophagis: 'sarcophagi', - saris: 'sari', + sarcophagus: 'sarcophagus', sassafras: 'sassafras', sassafrases: 'sassafras', savannahes: 'savannah', scabies: 'scabies', scampies: 'scampi', - scampis: 'scampi', scarves: 'scarf', - scherzis: 'scherzi', schnapps: 'schnapps', schooldays: 'schooldays', selfies: 'selfie', selves: 'self', semiotics: 'semiotics', - semis: 'semi', - shanghais: 'shanghai', sheaves: 'sheaf', shelves: 'shelf', shoes: 'shoe', @@ -613,38 +635,33 @@ export const SINGULAR_EXCEPTIONS: Record = { shuckses: 'shucks', sideburns: 'sideburns', sideways: 'sideways', - situs: 'situ', - skis: 'ski', + sinus: 'sinus', + sinusitis: 'sinusitis', + sinusitises: 'sinusitis', sloes: 'sloe', smithereens: 'smithereens', smoothes: 'smooth', - snafus: 'snafu', snowshoes: 'snowshoe', soapsuds: 'soapsuds', - solis: 'soli', sorties: 'sortie', sos: 'sos', spareribs: 'spareribs', sphinges: 'sphinges', - spumonis: 'spumoni', - staccatis: 'staccati', - staphylococcis: 'staphylococci', - stimulis: 'stimuli', + staphylococcus: 'staphylococcus', + stimulus: 'stimulus', stomachaches: 'stomachache', - streptococcis: 'streptococci', - stylis: 'styli', + streptococcus: 'streptococcus', + stylus: 'stylus', stymies: 'stymie', suds: 'suds', suffuses: 'suffuse', - sukiyakis: 'sukiyaki', sunglasses: 'sunglasses', - sushis: 'sushi', - swamis: 'swami', + surplus: 'surplus', sweatpants: 'sweatpants', sweeties: 'sweetie', - syllabis: 'syllabi', symbioses: 'symbiosis', - tableaus: 'tableau', + syphilis: 'syphilis', + syphilises: 'syphilis', tabus: 'tabu', takeaways: 'takeaways', taxies: 'taxi', @@ -654,34 +671,41 @@ export const SINGULAR_EXCEPTIONS: Record = { teargases: 'teargas', teargasses: 'teargasses', tectonics: 'tectonics', - tempis: 'tempi', - terminis: 'termini', - thalamis: 'thalami', + tendinitis: 'tendinitis', + tendinitises: 'tendinitis', + tendonitis: 'tendonitis', + tendonitises: 'tendonitis', + terminus: 'terminus', + tetanus: 'tetanus', + thalamus: 'thalamus', themselves: 'themselves', thermos: 'thermos', thermoses: 'thermos', - thesauris: 'thesauri', + thesaurus: 'thesaurus', thieves: 'thief', + this: 'this', + thises: 'this', thoraces: 'thoraces', thous: 'thou', throes: 'throe', thromboses: 'thrombosis', - thrus: 'thru', - thymis: 'thymi', + thus: 'thus', + thymus: 'thymus', thyselves: 'thyself', tiddlywinks: 'tiddlywinks', ties: 'tie', - timpanis: 'timpani', - tipis: 'tipi', tiptoes: 'tiptoe', toes: 'toe', - tofus: 'tofu', + tonsillitis: 'tonsillitis', + tonsillitises: 'tonsillitis', toothaches: 'toothache', topazes: 'topaz', torsis: 'torsi', + torus: 'torus', trans: 'trans', transfuses: 'transfuse', treas: 'treas', + trellis: 'trellis', trellises: 'trellis', triceps: 'triceps', tricepses: 'triceps', @@ -689,13 +713,12 @@ export const SINGULAR_EXCEPTIONS: Record = { triceratopses: 'triceratops', trifocals: 'trifocals', tripos: 'tripos', - trousseaus: 'trousseau', - tsunamis: 'tsunami', ttys: 'ttys', turves: 'turf', - tutus: 'tutu', tweezers: 'tweezers', - umbilicis: 'umbilici', + typhus: 'typhus', + tyrannosaurus: 'tyrannosaurus', + umbilicus: 'umbilicus', underclothes: 'underclothes', underlies: 'underlie', underpants: 'underpants', @@ -704,17 +727,16 @@ export const SINGULAR_EXCEPTIONS: Record = { unties: 'untie', ups: 'ups', upstairs: 'upstairs', - uteris: 'uteri', + uterus: 'uterus', veggies: 'veggie', + verdigris: 'verdigris', verdigrises: 'verdigris', - vermicellis: 'vermicelli', + versus: 'versus', vies: 'vie', - viiis: 'viii', - viis: 'vii', virtuosis: 'virtuosi', + viscus: 'viscus', vortices: 'vortex', - wadis: 'wadi', - wapitis: 'wapiti', + walrus: 'walrus', was: 'was', waterworks: 'waterworks', wedgies: 'wedgie', @@ -723,7 +745,6 @@ export const SINGULAR_EXCEPTIONS: Record = { wharves: 'wharf', wheelbases: 'wheelbase', whereabouts: 'whereabouts', - wikis: 'wiki', wildlives: 'wildlife', willies: 'willies', wives: 'wife', @@ -731,36 +752,11 @@ export const SINGULAR_EXCEPTIONS: Record = { wizzes: 'wizzes', woes: 'woe', wolves: 'wolf', - xciis: 'xcii', - xcis: 'xci', - xcviis: 'xcvii', - xcvis: 'xcvi', - xiiis: 'xiii', - xiis: 'xii', - xviiis: 'xviii', - xviis: 'xvii', - xvis: 'xvi', - xxiiis: 'xxiii', - xxiis: 'xxii', - xxis: 'xxi', - xxviiis: 'xxviii', - xxviis: 'xxvii', - xxvis: 'xxvi', - xxxiiis: 'xxxiii', - xxxiis: 'xxxii', - xxxis: 'xxxi', - xxxviiis: 'xxxviii', - xxxviis: 'xxxvii', - xxxvis: 'xxxvi', yes: 'yes', yeses: 'yes', - yetis: 'yeti', - yogis: 'yogi', yourselves: 'yourself', yous: 'you', yrs: 'yrs', zebus: 'zebu', zombies: 'zombie', - zombis: 'zombi', - zucchinis: 'zucchini', }; diff --git a/packages/inflekt/src/rules.ts b/packages/inflekt/src/rules.ts index 9a3f39c..0004e83 100644 --- a/packages/inflekt/src/rules.ts +++ b/packages/inflekt/src/rules.ts @@ -78,11 +78,18 @@ export const F_STEM_PLURALS: Array<[string, string]> = [ ]; /** - * Words that are already singular but look plural to the s$ rule: no English - * plural ends in "is" (iris, analysis, chassis) or "us" (bus, status, virus, - * anxious). + * Adjectives, never plurals: anxious, various, porous. No noun stem ends in + * "ou", so nothing here is ever a dropped-s away from a singular. + * + * There is no equivalent rule for the rest of "-is"/"-us", because none exists: + * "apis" and "iris" are both a vowel plus "s" (api + s, iri + s), so no suffix + * tells the plural from the singular. Whether a word is one or the other is a + * dictionary fact, and the dictionary-proven singulars live in + * SINGULAR_EXCEPTIONS as self-mappings (status -> status, analysis -> analysis). + * Words the dictionary does not know fall through to the generic "drop the s", + * which is what coined identifiers want: apis -> api, cpus -> cpu, uris -> uri. */ -const ALREADY_SINGULAR_REGEX = /(?:is|us)$/i; +const ADJECTIVE_OUS_REGEX = /ous$/i; /** * The only -ice plurals. The inflection library's rule is written `([m|l])ice`, @@ -212,7 +219,7 @@ export function singularizeByRules(word: string): string { const normalizedWord = normalizeMalformedDoubleS(word); const lowerWord = normalizedWord.toLowerCase(); - if (ALREADY_SINGULAR_REGEX.test(normalizedWord)) { + if (ADJECTIVE_OUS_REGEX.test(normalizedWord)) { return normalizedWord; }