accent counting fix
This commit is contained in:
parent
2e86e121c4
commit
672d9dac40
|
@ -5,7 +5,7 @@
|
|||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||
"@lingdocs/lingdocs-main": "^0.2.0",
|
||||
"@lingdocs/pashto-inflector": "^1.1.9",
|
||||
"@lingdocs/pashto-inflector": "^1.2.7",
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^11.1.0",
|
||||
"@testing-library/user-event": "^12.1.10",
|
||||
|
@ -31,9 +31,11 @@
|
|||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"start-w-user": "REACT_APP_ENV=dev react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
"eject": "react-scripts eject",
|
||||
"get-words": "node scripts/get-words.js"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
|
|
|
@ -2,20 +2,23 @@ const fs = require("fs");
|
|||
const fetch = require("node-fetch");
|
||||
const { readDictionary } = require("@lingdocs/pashto-inflector");
|
||||
const path = require("path");
|
||||
const verbsPath = path.join(".", "src", "words");
|
||||
const verbCollectionPath = path.join(verbsPath, "verb-categories");
|
||||
const nounAdjCollectionPath = path.join(verbsPath, "noun-adj-categories");
|
||||
const wordsPath = path.join(".", "src", "words");
|
||||
const wordsFile = "raw-words.ts";
|
||||
const verbCollectionPath = path.join(wordsPath, "verb-categories");
|
||||
const nounAdjCollectionPath = path.join(wordsPath, "noun-adj-categories");
|
||||
const verbTsFiles = fs.readdirSync(verbCollectionPath);
|
||||
const nounAdjTsFiles = fs.readdirSync(nounAdjCollectionPath);
|
||||
|
||||
const allVerbTsS = [...new Set(verbTsFiles.reduce((arr, fileName) => {
|
||||
const TsS = require(path.join("..", verbCollectionPath, fileName));
|
||||
const TsS = require(path.join("..", verbCollectionPath, fileName))
|
||||
.filter((v, i, a) => a.findIndex(x => x.ts === v.ts) === i);
|
||||
return [...arr, ...TsS];
|
||||
}, []))];
|
||||
|
||||
const allNounAdjTsS = [...new Set(nounAdjTsFiles.reduce((arr, fileName) => {
|
||||
const TsS = require(path.join("..", nounAdjCollectionPath, fileName));
|
||||
return [...arr, ...TsS.map(x => ({ ...x, category: path.parse(fileName).name }))];
|
||||
const TsS = require(path.join("..", nounAdjCollectionPath, fileName))
|
||||
.filter((v, i, a) => a.findIndex(x => x.ts === v.ts) === i);
|
||||
return [...arr, ...TsS];
|
||||
}, []))];
|
||||
|
||||
console.log("getting words from dictionary...");
|
||||
|
@ -24,17 +27,15 @@ fetch(process.env.LINGDOCS_DICTIONARY_URL).then(res => res.arrayBuffer()).then(b
|
|||
const dictionary = readDictionary(buffer);
|
||||
const entries = dictionary.entries;
|
||||
// MAKE VERBS FILE
|
||||
const allVerbs = getVerbsFromTsS(entries);
|
||||
const content = `const verbs = ${JSON.stringify(allVerbs)};
|
||||
export default verbs;`;
|
||||
fs.writeFileSync(path.join(verbsPath, "verbs.js"), content);
|
||||
|
||||
// MAKE NOUN-ADJ FILE
|
||||
const allNounsAdjs = getNounsAdjsFromTsS(entries);
|
||||
const content1 = `import { Types as T } from "@lingdocs/pashto-inflector";
|
||||
const nounsAdjs: { entry: T.DictionaryEntry, def: string, category: string }[] = ${JSON.stringify(allNounsAdjs)};
|
||||
export default nounsAdjs;`;
|
||||
fs.writeFileSync(path.join(verbsPath, "nouns-adjs.ts"), content1);
|
||||
const allWords = [
|
||||
...getVerbsFromTsS(entries),
|
||||
...getNounsAdjsFromTsS(entries),
|
||||
];
|
||||
const content = `
|
||||
// @ts-ignore
|
||||
const words: Word[] = ${JSON.stringify(allWords)};
|
||||
export default words;`;
|
||||
fs.writeFileSync(path.join(wordsPath, wordsFile), content);
|
||||
});
|
||||
|
||||
function getVerbsFromTsS(entries) {
|
||||
|
@ -55,7 +56,7 @@ function getVerbsFromTsS(entries) {
|
|||
complement,
|
||||
};
|
||||
}
|
||||
return { entry, def: item.e };
|
||||
return { entry };
|
||||
}).filter(x => x);
|
||||
if (missingEc.length !== 0) {
|
||||
console.log("Verbs missing ec:", missingEc);
|
||||
|
@ -73,7 +74,7 @@ function getNounsAdjsFromTsS(entries) {
|
|||
// const firstWord = entry.e.split(",")[0].split(";")[0].split("(")[0].trim();
|
||||
// console.log(firstWord, entry.f, entry.ts);
|
||||
// if (firstWord.contains(" ")) console.log("SPACE PRESENT");
|
||||
return { entry, def: item.e, category: item.category };
|
||||
return entry;
|
||||
}).filter(x => x);
|
||||
return b;
|
||||
// console.log(b.length, "number of nouns/adjs");
|
||||
|
|
|
@ -16,7 +16,11 @@ const chevStyle = {
|
|||
width: "3.5rem",
|
||||
};
|
||||
|
||||
export default function Carousel(props) {
|
||||
export default function Carousel<T>(props: {
|
||||
items: Array<T>,
|
||||
render: (item: T) => { title: JSX.Element | string, body: JSX.Element | string },
|
||||
stickyTitle?: boolean,
|
||||
}): JSX.Element {
|
||||
// console.log("pppp");
|
||||
// console.log(props.items);
|
||||
const [current, setCurrent] = useState(0);
|
||||
|
@ -50,11 +54,11 @@ export default function Carousel(props) {
|
|||
alt={"previous"}
|
||||
onClick={back}
|
||||
/>
|
||||
{title ?
|
||||
<div className="h5">{title}</div>
|
||||
:
|
||||
<div>{body}</div>
|
||||
}
|
||||
{title ?
|
||||
<div className="h5">{title}</div>
|
||||
:
|
||||
<div>{body}</div>
|
||||
}
|
||||
<img
|
||||
src={rightChevron}
|
||||
className="clickable mr-lg-3"
|
|
@ -10,7 +10,6 @@ function Chart({ titleRow, children, opts }: {
|
|||
children: T.PsString[][],
|
||||
opts: T.TextOptions,
|
||||
}) {
|
||||
console.log(children)
|
||||
return <table className="table">
|
||||
<thead>
|
||||
{titleRow && <tr>
|
||||
|
|
|
@ -6,21 +6,19 @@ import {
|
|||
removeFVarients,
|
||||
getEnglishWord,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import {
|
||||
isUnisexNoun,
|
||||
} from "../lib/type-predicates";
|
||||
import {
|
||||
equativeMachine,
|
||||
assembleEquativeOutput,
|
||||
AdjectiveInput,
|
||||
PredicateInput,
|
||||
isAdjectiveInput,
|
||||
EntityInput,
|
||||
isUnisexNounInput,
|
||||
UnisexNounInput,
|
||||
} from "../lib/equative-machine";
|
||||
import words from "../words/nouns-adjs";
|
||||
import { words } from "../words/words";
|
||||
|
||||
function uniqueSort(arr: AdjectiveInput[]): AdjectiveInput[];
|
||||
function uniqueSort(arr: UnisexNounInput[]): UnisexNounInput[];
|
||||
function uniqueSort(arr: (AdjectiveInput | UnisexNounInput)[]): (AdjectiveInput | UnisexNounInput)[] {
|
||||
function uniqueSort(arr: Adjective[]): Adjective[];
|
||||
function uniqueSort(arr: UnisexNoun[]): UnisexNoun[];
|
||||
function uniqueSort(arr: (Adjective | UnisexNoun)[]): (Adjective | UnisexNoun)[] {
|
||||
return arr
|
||||
.filter((v, i, a) => a.findIndex((e) => e.ts === v.ts) === i)
|
||||
.filter((e) => {
|
||||
|
@ -37,11 +35,11 @@ function uniqueSort(arr: (AdjectiveInput | UnisexNounInput)[]): (AdjectiveInput
|
|||
})
|
||||
.sort((a, b) => a.p.localeCompare(b.p));
|
||||
}
|
||||
|
||||
const inputs = {
|
||||
adjectives: uniqueSort(words.filter((w) => isAdjectiveInput(w.entry as EntityInput))
|
||||
.map((w) => w.entry as AdjectiveInput)),
|
||||
unisexNouns: uniqueSort(words.filter((w) => isUnisexNounInput(w.entry as EntityInput))
|
||||
.map((w) => w.entry as UnisexNounInput)),
|
||||
// TODO: instead - have them unique and sorted in the words.ts file
|
||||
adjectives: uniqueSort(words.adjectives),
|
||||
unisexNouns: uniqueSort(words.nouns.filter(x => isUnisexNoun(x)) as UnisexNoun[]),
|
||||
};
|
||||
|
||||
function makeBlock(e: PredicateInput): T.VerbBlock {
|
||||
|
@ -60,18 +58,18 @@ function makeBlock(e: PredicateInput): T.VerbBlock {
|
|||
}
|
||||
|
||||
type PredicateType = "adjectives" | "unisexNouns";
|
||||
// type SubjectType = "pronouns" | "nouns";
|
||||
type SubjectType = "pronouns" | "nouns";
|
||||
|
||||
// TODO: Plural nouns like shoode
|
||||
const defaultTs = 1527815306;
|
||||
const defaultPe = inputs.adjectives.find(a => a.ts === defaultTs) as AdjectiveInput;
|
||||
const defaultPe = inputs.adjectives.find(a => a.ts === defaultTs) || inputs.adjectives[0];
|
||||
|
||||
function EquativeExplorer() {
|
||||
// TODO: Use sticky state
|
||||
const predicateTypes: PredicateType[] = ["adjectives", "unisexNouns"];
|
||||
// const subjectTypes: SubjectType[] = ["pronouns", "nouns"];
|
||||
const subjectTypes: SubjectType[] = ["pronouns", "nouns"];
|
||||
const [predicate, setPredicate] = useState<number>(defaultTs);
|
||||
// const [subjectType, setSubjectType] = useState<SubjectType>("pronouns");
|
||||
const [subjectType, setSubjectType] = useState<SubjectType>("pronouns");
|
||||
const [predicateType, setPredicateType] = useState<PredicateType>("adjectives");
|
||||
|
||||
function makeOptionLabel(e: T.DictionaryEntry): string {
|
||||
|
@ -88,6 +86,11 @@ function EquativeExplorer() {
|
|||
setPredicateType(pt);
|
||||
setPredicate(inputs[pt][0].ts);
|
||||
}
|
||||
function handleSubjectTypeSelect(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const st = e.target.value as SubjectType;
|
||||
setSubjectType(st);
|
||||
// setPredicate(inputs[pt][0].ts);
|
||||
}
|
||||
// @ts-ignore
|
||||
const pe = (inputs[predicateType].find((a: AdjectiveInput | UnisexNounInput) => (
|
||||
a.ts === predicate
|
||||
|
@ -106,7 +109,7 @@ function EquativeExplorer() {
|
|||
|
||||
return <>
|
||||
<div className="d-flex flex-row">
|
||||
{/* <div className="form-group">
|
||||
<div className="form-group">
|
||||
<label htmlFor="subject-select"><strong>Subject:</strong></label>
|
||||
<div className="form-check">
|
||||
<input
|
||||
|
@ -116,13 +119,27 @@ function EquativeExplorer() {
|
|||
id="pronounsSubjectRadio"
|
||||
value={subjectTypes[0]}
|
||||
checked={subjectType === "pronouns"}
|
||||
onChange={handlePredicateTypeSelect}
|
||||
onChange={handleSubjectTypeSelect}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor="adjectivesPredicateRadio">
|
||||
<label className="form-check-label" htmlFor="pronounsSubjectRadio">
|
||||
Pronouns
|
||||
</label>
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="form-check">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="radio"
|
||||
name="nounsSubjectRadio"
|
||||
id="nounsSubjectRadio"
|
||||
value={subjectTypes[0]}
|
||||
checked={subjectType === "nouns"}
|
||||
onChange={handleSubjectTypeSelect}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor="nounsSubjectRadio">
|
||||
Nouns
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="predicate-select"><strong>Predicate:</strong></label>
|
||||
<div className="form-check">
|
||||
|
@ -160,7 +177,7 @@ function EquativeExplorer() {
|
|||
value={predicate}
|
||||
onChange={handlePredicateSelect}
|
||||
>
|
||||
{inputs[predicateType].map((e: AdjectiveInput | UnisexNounInput) => (
|
||||
{inputs[predicateType].map((e: Adjective | UnisexNoun) => (
|
||||
<option key={e.ts+"s"} value={e.ts}>{makeOptionLabel(e)}</option>
|
||||
))}
|
||||
</select>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import React from "react";
|
||||
import Carousel from "./Carousel";
|
||||
import {
|
||||
InlinePs,
|
||||
|
@ -6,24 +5,37 @@ import {
|
|||
InflectionsTable,
|
||||
inflectWord,
|
||||
defaultTextOptions as opts,
|
||||
getEnglishWord,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
|
||||
function InflectionCarousel({ items }: any) {
|
||||
function InflectionCarousel({ items }: { items: (Noun | Adjective)[] }) {
|
||||
if (!items.length) {
|
||||
return "no items for carousel";
|
||||
}
|
||||
return (
|
||||
<div className="mt-3">
|
||||
<Carousel items={items} render={(item: any) => {
|
||||
const infOut = inflectWord(item.entry);
|
||||
<Carousel items={items} render={item => {
|
||||
const e = getEnglishWord(item);
|
||||
const english = e === undefined
|
||||
? item.e
|
||||
: typeof e === "string"
|
||||
? e
|
||||
: e.singular !== undefined
|
||||
? e.singular
|
||||
: item.e;
|
||||
const infOut = inflectWord(item);
|
||||
if (!infOut || !infOut.inflections) {
|
||||
return (
|
||||
return {
|
||||
title: "Oops! 🤷♂️",
|
||||
// @ts-ignore
|
||||
<div>Oops! No inflections for <InlinePs opts={opts} />{item.entry}</div>
|
||||
);
|
||||
body: <div>Oops! No inflections for <InlinePs opts={opts}>{item}</InlinePs></div>,
|
||||
};
|
||||
}
|
||||
return {
|
||||
// @ts-ignore
|
||||
title: <InlinePs opts={opts} ps={{
|
||||
...removeFVarients(item.entry),
|
||||
e: item.def,
|
||||
...removeFVarients(item),
|
||||
e: english,
|
||||
}} />,
|
||||
body: <InflectionsTable
|
||||
inf={infOut.inflections}
|
||||
|
|
|
@ -158,7 +158,7 @@ Interestingly, there is also a separate [habitual equative](https://en.wikipedia
|
|||
<img className="img-fluid mb-4" src={grassBeWet} />
|
||||
|
||||
<div style={{ overflowX: "auto" }}>
|
||||
<table class="table" style={{ minWidth: "400px" }}>
|
||||
<table className="table" style={{ minWidth: "400px" }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Form</th>
|
||||
|
|
|
@ -19,16 +19,16 @@ import * as otherEquatives from "!babel-loader!@lingdocs/mdx-loader!./equatives/
|
|||
// @ts-ignore
|
||||
import * as equativeExplorer from "!babel-loader!@lingdocs/mdx-loader!./equatives/equative-explorer.mdx";
|
||||
|
||||
// @ts-ignore
|
||||
import * as nounsGender from "!babel-loader!@lingdocs/mdx-loader!./nouns/nouns-gender.mdx";
|
||||
// @ts-ignore
|
||||
import * as nounsUnisex from "!babel-loader!@lingdocs/mdx-loader!./nouns/nouns-unisex.mdx";
|
||||
// @ts-ignore
|
||||
import * as nounsPlural from "!babel-loader!@lingdocs/mdx-loader!./nouns/nouns-plural.mdx";
|
||||
// @ts-ignore
|
||||
import * as arabicPlurals from "!babel-loader!@lingdocs/mdx-loader!./nouns/arabic-plurals.mdx";
|
||||
// @ts-ignore
|
||||
import * as bundledPlurals from "!babel-loader!@lingdocs/mdx-loader!./nouns/bundled-plurals.mdx";
|
||||
// // @ts-ignore
|
||||
// import * as nounsGender from "!babel-loader!@lingdocs/mdx-loader!./nouns/nouns-gender.mdx";
|
||||
// // @ts-ignore
|
||||
// import * as nounsUnisex from "!babel-loader!@lingdocs/mdx-loader!./nouns/nouns-unisex.mdx";
|
||||
// // @ts-ignore
|
||||
// import * as nounsPlural from "!babel-loader!@lingdocs/mdx-loader!./nouns/nouns-plural.mdx";
|
||||
// // @ts-ignore
|
||||
// import * as arabicPlurals from "!babel-loader!@lingdocs/mdx-loader!./nouns/arabic-plurals.mdx";
|
||||
// // @ts-ignore
|
||||
// import * as bundledPlurals from "!babel-loader!@lingdocs/mdx-loader!./nouns/bundled-plurals.mdx";
|
||||
|
||||
// @ts-ignore
|
||||
import * as verbAspect from "!babel-loader!@lingdocs/mdx-loader!./verbs/verb-aspect.mdx";
|
||||
|
@ -105,32 +105,32 @@ const contentTree = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: "Nouns",
|
||||
subdirectory: "nouns",
|
||||
chapters: [
|
||||
{
|
||||
import: nounsGender,
|
||||
slug: "nouns-gender",
|
||||
},
|
||||
{
|
||||
import: nounsUnisex,
|
||||
slug: "nouns-unisex",
|
||||
},
|
||||
{
|
||||
import: nounsPlural,
|
||||
slug: "nouns-plural",
|
||||
},
|
||||
{
|
||||
import: arabicPlurals,
|
||||
slug: "arabic-plurals",
|
||||
},
|
||||
{
|
||||
import: bundledPlurals,
|
||||
sluge: "bundled-plurals",
|
||||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
// heading: "Nouns",
|
||||
// subdirectory: "nouns",
|
||||
// chapters: [
|
||||
// {
|
||||
// import: nounsGender,
|
||||
// slug: "nouns-gender",
|
||||
// },
|
||||
// {
|
||||
// import: nounsUnisex,
|
||||
// slug: "nouns-unisex",
|
||||
// },
|
||||
// {
|
||||
// import: nounsPlural,
|
||||
// slug: "nouns-plural",
|
||||
// },
|
||||
// {
|
||||
// import: arabicPlurals,
|
||||
// slug: "arabic-plurals",
|
||||
// },
|
||||
// {
|
||||
// import: bundledPlurals,
|
||||
// sluge: "bundled-plurals",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
heading: "Verbs",
|
||||
subdirectory: "verbs",
|
||||
|
|
|
@ -25,10 +25,17 @@ import {
|
|||
} from "@lingdocs/pashto-inflector";
|
||||
import shuffle from "../../lib/shuffle-array";
|
||||
import InflectionCarousel from "../../components/InflectionCarousel";
|
||||
import words from "../../words/nouns-adjs";
|
||||
import { words } from "../../words/words";
|
||||
import Link from "../../components/Link";
|
||||
import Table from "../../components/Table";
|
||||
import { startingWord } from "../../lib/starting-word";
|
||||
import {
|
||||
isFemNoun,
|
||||
isPattern6FemNoun,
|
||||
isPattern7FemNoun,
|
||||
} from "../../lib/type-predicates";
|
||||
|
||||
export const femNouns = words.nouns.filter(w => isFemNoun(w));
|
||||
|
||||
The <Link to="/inflection/inflection-patterns/">5 basic patterns in the last chapter</Link> work with both masculine and feminine words (nouns and adjectives).
|
||||
|
||||
|
@ -36,10 +43,6 @@ There are also a few more patterns that are only for **feminine nouns**.
|
|||
|
||||
## Feminine Nouns Ending in <InlinePs opts={opts} ps={{ p: "ي", f: "ee" }} />
|
||||
|
||||
<InflectionCarousel items={startingWord(words, "ee-fem", "آزادي")} />
|
||||
<InflectionCarousel items={startingWord(femNouns.filter(isPattern6FemNoun), "آزادي")} />
|
||||
|
||||
**Note:** This only works with **inanimate nouns**. (ie. words for people like <InlinePs opts={opts} ps={{ p: "باجي", f: "baajée", e: "older sister" }} /> will not inflect like this.)
|
||||
|
||||
## Feminine Nouns Ending in <InlinePs opts={opts} ps={{ p: "ۍ", f: "uy" }} />
|
||||
|
||||
<InflectionCarousel items={startingWord(words, "uy-fem", "هګۍ")} />
|
||||
|
|
|
@ -22,11 +22,25 @@ import {
|
|||
InflectionsTable,
|
||||
inflectWord,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import {
|
||||
isNounOrVerb,
|
||||
isPattern1Word,
|
||||
isPattern2Word,
|
||||
isPattern3Word,
|
||||
isPattern4Word,
|
||||
isPattern5Word,
|
||||
isUnisexNoun,
|
||||
} from "../../lib/type-predicates";
|
||||
import InflectionCarousel from "../../components/InflectionCarousel";
|
||||
import words from "../../words/nouns-adjs";
|
||||
import { words as w } from "../../words/words";
|
||||
import { startingWord } from "../../lib/starting-word";
|
||||
import Link from "../../components/Link";
|
||||
|
||||
export const words = [
|
||||
...w.nouns.filter(isUnisexNoun),
|
||||
...w.adjectives,
|
||||
];
|
||||
|
||||
In the <Link to="/inflection/inflection-intro/">previous chapter</Link> we talked about **why** words inflect. Now we will explain **how** words inflect. What actualy happens to them? How do they change?
|
||||
|
||||
Like many things in Pashto, inflection can seem mysterious and intimidating. Why do the words change so much, and how!? 😩 But, we'll see that there are just a few basic patterns that the words follow, and it's not so scary or confusing after all.
|
||||
|
@ -42,7 +56,10 @@ These words always end in:
|
|||
- **Masculine:** - consonant or a shwa (<InlinePs opts={opts} ps={{ p: "ـه", f: "-u" }} /> vowel)
|
||||
- **Feminine:** - <InlinePs opts={opts} ps={{ p: "ـه", f: "-a" }} />
|
||||
|
||||
<InflectionCarousel items={startingWord(words, "basic-unisex", "غټ")} />
|
||||
<InflectionCarousel items={startingWord(
|
||||
words.filter(isPattern1Word),
|
||||
"غټ",
|
||||
)} />
|
||||
|
||||
<details>
|
||||
|
||||
|
@ -74,13 +91,19 @@ Notice how it does not use the first feminine inflection <InlinePs opts={opts} p
|
|||
|
||||
## 2. Words ending in an unstressed <InlinePs opts={opts} ps={{ p: "ی", f: "ey" }} />
|
||||
|
||||
<InflectionCarousel items={startingWord(words, "ey-unstressed-unisex", "ستړی")} />
|
||||
<InflectionCarousel items={startingWord(
|
||||
words.filter(isPattern2Word),
|
||||
"ستړی",
|
||||
)} />
|
||||
|
||||
## 3. Words ending in a stressed <InlinePs opts={opts} ps={{ p: "ی", f: "éy" }} />
|
||||
|
||||
This is very similar to pattern #2, but with the stress on the last syllable the feminine inflection changes.
|
||||
|
||||
<InflectionCarousel items={startingWord(words, "ey-stressed-unisex", "لومړی")} />
|
||||
<InflectionCarousel items={startingWord(
|
||||
words.filter(isPattern3Word),
|
||||
"لومړی",
|
||||
)} />
|
||||
|
||||
## 4. Words with the "Pashtoon" pattern
|
||||
|
||||
|
@ -89,7 +112,10 @@ These words are a little irregular but you can see a common patten based around:
|
|||
- lengthening the 1st masculine inflection with <InlinePs opts={opts} ps={{ p: "ا ـ ـه", f: "aa _ u" }} />
|
||||
- shortening the other forms and adding the <InlinePs opts={opts} ps={{ p: "ـه", f: "-a" }} />, <InlinePs opts={opts} ps={{ p: "ـې", f: "-e" }} />, <InlinePs opts={opts} ps={{ p: "ـو", f: "-o" }} /> endings
|
||||
|
||||
<InflectionCarousel items={startingWord(words, "aanu-unisex", "پښتون")} />
|
||||
<InflectionCarousel items={startingWord(
|
||||
words.filter(isPattern4Word),
|
||||
"پښتون",
|
||||
)} />
|
||||
|
||||
**Note**: Nouns in this pattern will often only use the first inflection for the plural. Adjectives will use the 1st inflection for all 3 reasons.
|
||||
|
||||
|
@ -97,7 +123,10 @@ These words are a little irregular but you can see a common patten based around:
|
|||
|
||||
These are also a little irregular but instead of lengthening the 1st masculine inflection they compress it as well and take just an <InlinePs opts={opts} ps={{ p: "ـه", f: "-u" }} /> on the end.
|
||||
|
||||
<InflectionCarousel items={startingWord(words, "short-irreg-unisex", "غل")} />
|
||||
<InflectionCarousel items={startingWord(
|
||||
words.filter(isPattern5Word),
|
||||
"غل",
|
||||
)} />
|
||||
|
||||
## Not all words inflect
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ This last kind is probably the newest and most surprising to language learners.
|
|||
|
||||
export function SandwichTable() {
|
||||
return (
|
||||
<table class="table table-bordered my-3">
|
||||
<table className="table table-bordered my-3">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Sandwich</th>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-crown" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="icon icon-tabler icon-tabler-crown" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 6l4 6l5 -4l-2 10h-14l-2 -10l5 4z" />
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 340 B After Width: | Height: | Size: 344 B |
|
@ -14,17 +14,17 @@ import psmd from "../../lib/psmd";
|
|||
import Carousel from "../../components/Carousel";
|
||||
import Link from "../../components/Link";
|
||||
import Formula from "../../components/formula/Formula";
|
||||
import verbs from "../../words/verbs";
|
||||
import { words } from "../../words/words";
|
||||
import shuffleArray from "../../lib/shuffle-array";
|
||||
import imperfectiveFuture from "./imperfective-future-graph.svg";
|
||||
import perfectiveFuture from "./perfective-future-graph.svg";
|
||||
|
||||
export const basicVerbs = verbs.filter((v) => !v.entry.c?.includes("gramm. trans."));
|
||||
export const basicVerbs = words.verbs.filter((v) => !v.entry.c?.includes("gramm. trans."));
|
||||
|
||||
There are two kinds of future forms in Pashto:
|
||||
|
||||
2. <i class="fas fa-video" /> Imperfective Future
|
||||
1. <i class="fas fa-camera" /> Perfective Future
|
||||
2. <i className="fas fa-video" /> Imperfective Future
|
||||
1. <i className="fas fa-camera" /> Perfective Future
|
||||
|
||||
## Imperfective Future
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import psmd from "../../lib/psmd";
|
|||
import Carousel from "../../components/Carousel";
|
||||
import Link from "../../components/Link";
|
||||
import Formula from "../../components/formula/Formula";
|
||||
import verbs from "../../words/verbs";
|
||||
import { words } from "../../words/words";
|
||||
import shuffleArray from "../../lib/shuffle-array";
|
||||
import imperfectiveImperative from "./imperfective-imperative.svg";
|
||||
import perfectiveImperative from "./perfective-imperative.svg";
|
||||
|
@ -22,10 +22,10 @@ The imperative form is used for **giving commands** (telling people to do things
|
|||
|
||||
There are two forms of the imperative:
|
||||
|
||||
1. <i class="fas fa-video" /> Imperfective Imperative
|
||||
2. <i class="fas fa-camera" /> Perfective Imperative
|
||||
1. <i className="fas fa-video" /> Imperfective Imperative
|
||||
2. <i className="fas fa-camera" /> Perfective Imperative
|
||||
|
||||
export const basicVerbs = verbs.filter((v) => !v.entry.c?.includes("gramm. trans."));
|
||||
export const basicVerbs = words.verbs.filter((v) => !v.entry.c?.includes("gramm. trans."));
|
||||
|
||||
## Imperfective Imperative
|
||||
|
||||
|
@ -33,7 +33,7 @@ export const basicVerbs = verbs.filter((v) => !v.entry.c?.includes("gramm. trans
|
|||
Imperfective Stem + <Link to="/verbs/verb-endings/#imperative-verb-endings">Imperative Ending</Link>
|
||||
</Formula>
|
||||
|
||||
The <i class="fas fa-video" /> **imperfective imperative** is used when you want to tell someone to something repeatedly, in general, or if you're wanting them to get going on action that will be ongoing.
|
||||
The <i className="fas fa-video" /> **imperfective imperative** is used when you want to tell someone to something repeatedly, in general, or if you're wanting them to get going on action that will be ongoing.
|
||||
|
||||
<div className="text-center">
|
||||
<img src={imperfectiveImperative} alt="" className="img-fluid" />
|
||||
|
@ -45,7 +45,7 @@ The <i class="fas fa-video" /> **imperfective imperative** is used when you want
|
|||
Perfective Stem + <Link to="/verbs/verb-endings/#imperative-verb-endings">Imperative Ending</Link>
|
||||
</Formula>
|
||||
|
||||
The <i class="fas fa-camera" /> **perfective imperative** is used when you want to tell someone to do something is a one time, complete action. You are not thinking of the action as a process or as something that will be repeated, you are just telling the person to *get the action done*.
|
||||
The <i className="fas fa-camera" /> **perfective imperative** is used when you want to tell someone to do something is a one time, complete action. You are not thinking of the action as a process or as something that will be repeated, you are just telling the person to *get the action done*.
|
||||
|
||||
<div className="text-center">
|
||||
<img src={perfectiveImperative} alt="" className="img-fluid" />
|
||||
|
@ -75,15 +75,15 @@ The <i class="fas fa-camera" /> **perfective imperative** is used when you want
|
|||
For Pashto learners, having a choice between the perfective and imperfective imperatives is *not* something we are accustomed to. And so, it takes a lot of time to get used to the difference, and to choose the right form while speaking.
|
||||
|
||||
<div className="my-3" style={{ overflowX: "auto" }}>
|
||||
<table class="table">
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
<div><i class="fas fa-video" /> Imperfective</div>
|
||||
<div><i className="fas fa-video" /> Imperfective</div>
|
||||
<div><samp>w/ imperfective stem</samp></div>
|
||||
</th>
|
||||
<th scope="col">
|
||||
<div><i class="fas fa-camera" /> Perfective</div>
|
||||
<div><i className="fas fa-camera" /> Perfective</div>
|
||||
<div><samp>w/ perfective stem</samp></div>
|
||||
</th>
|
||||
</tr>
|
||||
|
@ -169,7 +169,7 @@ You will notice there are only two <Link to="/verbs/verb-endings/#imperative-ver
|
|||
- <InlinePs opts={opts} ps={{ p: "ـئ", f: "-eyy" }} /> - used for addressing a plural 2nd person <InlinePs opts={opts} ps={{ p: "تاسو", f: "taaso", e: "you" }} />, either because you're talking to a group of people, or you're being extra respectful with one person.
|
||||
|
||||
<div className="my-3" style={{ overflowX: "auto" }}>
|
||||
<table class="table">
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Singular</th>
|
||||
|
@ -201,7 +201,7 @@ You will notice there are only two <Link to="/verbs/verb-endings/#imperative-ver
|
|||
<InlinePs opts={opts} ps={{ p: "مه", f: "má" }} /> + <Link to="/verbs/imperative-verbs/#imperfective-imperative">Imperfective Imperative</Link>
|
||||
</Formula>
|
||||
|
||||
With this form, you can't specify whether you're talking about about the action in a <i class="fas fa-camera" /> perfective or <i class="fas fa-video" /> imperfective way. You're just saying "don't do it!", either one time or in general.
|
||||
With this form, you can't specify whether you're talking about about the action in a <i className="fas fa-camera" /> perfective or <i className="fas fa-video" /> imperfective way. You're just saying "don't do it!", either one time or in general.
|
||||
|
||||
<Examples opts={opts}>{[
|
||||
{ p: "مه راځه!", f: "má raadza", e: "Don't come!" },
|
||||
|
|
|
@ -13,11 +13,13 @@ import psmd from "../../lib/psmd";
|
|||
import Carousel from "../../components/Carousel";
|
||||
import Link from "../../components/Link";
|
||||
import Formula from "../../components/formula/Formula";
|
||||
import verbs from "../../words/verbs";
|
||||
import { words } from "../../words/words";
|
||||
import shuffleArray from "../../lib/shuffle-array";
|
||||
import realityGraph from "./reality-graph.svg";
|
||||
import presentTime from "./present-time.svg";
|
||||
|
||||
export const verbs = words.verbs;
|
||||
|
||||
export const basicVerbs = verbs.filter((v) => !v.entry.c?.includes("gramm. trans."));
|
||||
|
||||
The first verb form we'll learn is the **present**. This will be the first tool in our toolbox of verb forms. 🧰 With each verb form we'll learn two things:
|
||||
|
|
|
@ -25,12 +25,14 @@ import {
|
|||
} from "@lingdocs/pashto-inflector";
|
||||
import shuffle from "../../lib/shuffle-array";
|
||||
import Carousel from "../../components/Carousel";
|
||||
import verbs from "../../words/verbs";
|
||||
import { words } from "../../words/words";
|
||||
import Link from "../../components/Link";
|
||||
import verbTreeBase from "./verb-tree-base.svg";
|
||||
import verbTreePastPresent from "./verb-tree-past-present.svg";
|
||||
import verbTreeImperfectivePerfective from "./verb-tree-imperfective-perfective.svg";
|
||||
|
||||
export const verbs = words.verbs;
|
||||
|
||||
export const opts = defaultTextOptions;
|
||||
|
||||
export function InfoCarousel({ items, highlighted, hidePastParticiple }) {
|
||||
|
|
|
@ -14,30 +14,30 @@ import psmd from "../../lib/psmd";
|
|||
import Carousel from "../../components/Carousel";
|
||||
import Link from "../../components/Link";
|
||||
import Formula from "../../components/formula/Formula";
|
||||
import verbs from "../../words/verbs";
|
||||
import { words } from "../../words/words";
|
||||
import shuffleArray from "../../lib/shuffle-array";
|
||||
import presentInReality from "./present-in-reality.svg";
|
||||
import subjunctiveAboveReality from "./subjunctive-above-reality.svg";
|
||||
|
||||
export const basicVerbs = verbs.filter((v) => !v.entry.c?.includes("gramm. trans."));
|
||||
export const basicVerbs = words.verbs.filter((v) => !v.entry.c?.includes("gramm. trans."));
|
||||
|
||||
The **subjunctive** is a very important verb form in Pashto, but it's often ignored by English-speaking learners because we don't really have anything like it in English. So, we need to understand what it is, and then train our brains to reach for it and use it in the right situations!
|
||||
|
||||
## Introducing the Subjunctive
|
||||
|
||||
The subjunctive is like the <i class="fas fa-camera" /> perfective cousin of the <Link to="/verbs/present-verbs">present</Link> form.
|
||||
The subjunctive is like the <i className="fas fa-camera" /> perfective cousin of the <Link to="/verbs/present-verbs">present</Link> form.
|
||||
|
||||
<div className="text-center">
|
||||
<img src={cousins} alt="" className="img-fluid" />
|
||||
</div>
|
||||
|
||||
We said we use the <Link to="/verbs/present-verbs">present</Link> form to talk about things that are happening, or generally happen in reality. Naturally this was <i class="fas fa-video" /> imperfective because the event is either ongoing or reccuring.
|
||||
We said we use the <Link to="/verbs/present-verbs">present</Link> form to talk about things that are happening, or generally happen in reality. Naturally this was <i className="fas fa-video" /> imperfective because the event is either ongoing or reccuring.
|
||||
|
||||
<div className="text-center">
|
||||
<img src={presentInReality} alt="" className="img-fluid" />
|
||||
</div>
|
||||
|
||||
But the subjunctive is <i class="fas fa-camera" /> perfective because instead of talking about something happening in reality, we're talking **about the idea of something happening or not**. We're looking about the action or event *from the outside as a whole*.
|
||||
But the subjunctive is <i className="fas fa-camera" /> perfective because instead of talking about something happening in reality, we're talking **about the idea of something happening or not**. We're looking about the action or event *from the outside as a whole*.
|
||||
|
||||
<div className="text-center">
|
||||
<img src={subjunctiveAboveReality} alt="" className="img-fluid" />
|
||||
|
|
|
@ -17,11 +17,11 @@ Pashto verbs express actions by looking at them from two different [aspects](htt
|
|||
|
||||
The aspects can be thought of as two different *perspectives* or *ways of looking* at an action:
|
||||
|
||||
<table class="table">
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"><i class="fas fa-video" /> Imperfective</th>
|
||||
<th scope="col"><i class="fas fa-camera" /> Perfective</th>
|
||||
<th scope="col"><i className="fas fa-video" /> Imperfective</th>
|
||||
<th scope="col"><i className="fas fa-camera" /> Perfective</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -68,7 +68,7 @@ As we saw in the examples above, in English we are used to using these two diffe
|
|||
|
||||
For example, when making commands in Pashto we have to choose which aspect we are talking about. In English we can only say "clean your room!" But in Pashto we have to think, are we talking about a one-time request to get something done (perfective <i className="fas fa-camera" />), or asking someone to work on something as on ongoing, repeated thing (imperfective <i className="fas fa-video" />)?
|
||||
|
||||
<table class="table" style={{ tableLayout: "fixed" }}>
|
||||
<table className="table" style={{ tableLayout: "fixed" }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"><i className="fas fa-video fa-lg" /> Imperfective</th>
|
||||
|
@ -97,7 +97,7 @@ For example, when making commands in Pashto we have to choose which aspect we ar
|
|||
|
||||
Or when talking about things in the future tense, we face the same choice of aspect:
|
||||
|
||||
<table class="table" style={{ tableLayout: "fixed" }}>
|
||||
<table className="table" style={{ tableLayout: "fixed" }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"><i className="fas fa-video fa-lg" /> Imperfective</th>
|
||||
|
@ -126,7 +126,7 @@ Or when talking about things in the future tense, we face the same choice of asp
|
|||
|
||||
Even when talking about ability in the past tense, you still have to choose an aspect!
|
||||
|
||||
<table class="table" style={{ tableLayout: "fixed" }}>
|
||||
<table className="table" style={{ tableLayout: "fixed" }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"><i className="fas fa-video fa-lg" /> Imperfective</th>
|
||||
|
|
|
@ -16,7 +16,7 @@ The five ی letters (<InlinePs opts={opts} ps={{ p: "پینځه یېګانې", f
|
|||
|
||||
Below are the five ی's as they are used in most dictionaries, written material, and in education and official language in Afghanistan. Click to hear the sound of each letter.
|
||||
|
||||
<table class="table table-bordered my-3 text-center" style={{ margin: "0 auto", maxWidth: "400px" }}>
|
||||
<table className="table table-bordered my-3 text-center" style={{ margin: "0 auto", maxWidth: "400px" }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Letter</th>
|
||||
|
@ -196,7 +196,7 @@ Here's a chart of 3 different systems used in Pakistan, with the differences fro
|
|||
### Comparison Chart
|
||||
|
||||
<div className="my-3" style={{ overflowX: "auto" }}>
|
||||
<table class="table table-bordered text-center" style={{ maxWidth: "500px", tableLayout: "fixed", margin: "0 auto" }}>
|
||||
<table className="table table-bordered text-center" style={{ maxWidth: "500px", tableLayout: "fixed", margin: "0 auto" }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">AF</th>
|
||||
|
@ -264,7 +264,7 @@ This system was used in older writings and only uses three ی letters (ے، ی،
|
|||
|
||||
Earlier we noted that ـئـ shows up in the middle of loan words, but in Pakistan they often use it as a "y" transition before a long "ee" sound in other Pashto words as well. For example:
|
||||
|
||||
<table class="table">
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Afghanistan</th>
|
||||
|
@ -283,7 +283,7 @@ Earlier we noted that ـئـ shows up in the middle of loan words, but in Pakist
|
|||
|
||||
Instead of the ی at the end of long-vowel dipthongs, the letter ئ is used in Pakistan.
|
||||
|
||||
<table class="table">
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Afghanistan</th>
|
||||
|
@ -314,7 +314,7 @@ In Pakistan, Pashto is more of a spoken language than a written one. Because of
|
|||
|
||||
One very common mistake in Pakistan is the use of ے instead of ې. This might be because in the old writing system the letter ے was used for both <InlinePs opts={opts} ps={{ p: "ے", f: "ey" }} /> and <InlinePs opts={opts} ps={{ p: "ې", f: "e" }} />, but it is probably also due to a habit of <strong>inflecting words with ے as they are in Urdu</strong>.
|
||||
|
||||
<table class="table">
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Correct PK w/ ي ✔</th>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import React from "react";
|
||||
import {
|
||||
getRandomFromList,
|
||||
makeProgress,
|
||||
|
@ -9,44 +8,87 @@ import {
|
|||
Types as T,
|
||||
Examples,
|
||||
defaultTextOptions as opts,
|
||||
endsWith,
|
||||
pashtoConsonants,
|
||||
inflectWord,
|
||||
isUnisexSet,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import words from "../../words/nouns-adjs";
|
||||
import { words } from "../../words/words";
|
||||
import {
|
||||
firstVariation,
|
||||
} from "../../lib/text-tools";
|
||||
import {
|
||||
isMascNoun,
|
||||
isFemNoun,
|
||||
isUnisexNoun,
|
||||
} from "../../lib/type-predicates";
|
||||
import { categorize } from "../../lib/categorize";
|
||||
|
||||
const genders: T.Gender[] = ["masc", "fem"];
|
||||
|
||||
// const masc = words.filter((w) => w.entry.c === "n. m.");
|
||||
// const fem = words.filter((w) => w.entry.c === "n. f.");
|
||||
type CategorySet = Record<string, { category: string, def: string, entry: T.DictionaryEntry }[]>;
|
||||
const types: Record<string, CategorySet> = {
|
||||
masc: {
|
||||
consonantMasc: words.filter((w) => w.category === "consonant-masc"),
|
||||
eyMasc: words.filter((w) => w.category === "ey-masc"),
|
||||
uMasc: words.filter((w) => w.category === "u-masc"),
|
||||
yMasc: words.filter((w) => w.category === "y-masc"),
|
||||
},
|
||||
fem: {
|
||||
aaFem: words.filter((w) => w.category === "aa-fem"),
|
||||
eeFem: words.filter((w) => w.category === "ee-fem"),
|
||||
uyFem: words.filter((w) => w.category === "uy-fem"),
|
||||
aFem: words.filter((w) => w.category === "a-fem"),
|
||||
eFem: words.filter((w) => w.category === "e-fem"),
|
||||
},
|
||||
// TODO add ې fem words and balance gender
|
||||
const mascNouns = words.nouns.filter(isMascNoun);
|
||||
const femNouns = [
|
||||
...words.nouns.filter(isFemNoun),
|
||||
...getFemVersions(mascNouns.filter(isUnisexNoun)),
|
||||
];
|
||||
|
||||
const types = {
|
||||
masc: categorize<MascNoun, {
|
||||
consonantMasc: MascNoun[],
|
||||
eyMasc: MascNoun[],
|
||||
uMasc: MascNoun[],
|
||||
yMasc: MascNoun[],
|
||||
}>(mascNouns, {
|
||||
consonantMasc: endsWith([{ p: pashtoConsonants }, { p: "و", f: "w" }]),
|
||||
eyMasc: endsWith({ p: "ی", f: "ey" }),
|
||||
uMasc: endsWith({ p: "ه", f: "u" }),
|
||||
yMasc: endsWith([{ p: "ای", f: "aay" }, { p: "وی", f: "ooy" }]),
|
||||
}),
|
||||
fem: categorize<FemNoun, {
|
||||
aaFem: FemNoun[],
|
||||
eeFem: FemNoun[],
|
||||
uyFem: FemNoun[],
|
||||
aFem: FemNoun[],
|
||||
eFem: FemNoun[],
|
||||
}>(femNouns, {
|
||||
aaFem: endsWith({ p: "ا", f: "aa" }),
|
||||
eeFem: endsWith({ p: "ي", f: "ee" }),
|
||||
uyFem: endsWith({ p: "ۍ" }),
|
||||
aFem: endsWith([{ p: "ه", f: "a" }, { p: "ح", f: "a" }]),
|
||||
eFem: endsWith({ p: "ې" }),
|
||||
}),
|
||||
};
|
||||
|
||||
function getFemVersions(uns: UnisexNoun[]): FemNoun[] {
|
||||
return uns.map((n) => {
|
||||
const infs = inflectWord(n);
|
||||
if (!infs || !infs.inflections) return undefined;
|
||||
if (!isUnisexSet(infs.inflections)) return undefined;
|
||||
return {
|
||||
e: n.e,
|
||||
...infs.inflections.fem[0][0],
|
||||
} as T.DictionaryEntry;
|
||||
}).filter(n => !!n) as FemNoun[];
|
||||
}
|
||||
|
||||
function flatten<T>(o: Record<string, T[]>): T[] {
|
||||
return Object.values(o).flat();
|
||||
}
|
||||
|
||||
function nounNotIn(st: Noun[]): (n: Noun | T.DictionaryEntry) => boolean {
|
||||
return (n: T.DictionaryEntry) => !st.find(x => x.ts === n.ts);
|
||||
}
|
||||
|
||||
type CategorySet = Record<string, Noun[]>;
|
||||
// for some reason we need to use this CategorySet type here... 🤷♂️
|
||||
const exceptions: Record<string, CategorySet> = {
|
||||
masc: {
|
||||
exceptionPeopleMasc: words.filter((w) => w.category === "exception-people-masc"),
|
||||
exceptionMasc: mascNouns.filter(nounNotIn(flatten(types.masc))),
|
||||
},
|
||||
fem: {
|
||||
consonantFem: words.filter((w) => w.category === "consonant-fem"),
|
||||
exceptionPeopleFem: words.filter((w) => w.category === "exception-people-fem"),
|
||||
exceptionFem: femNouns.filter(nounNotIn(flatten(types.fem))),
|
||||
},
|
||||
}
|
||||
// consonantFem: words.filter((w) => w.category === "consonant-fem"),
|
||||
};
|
||||
|
||||
const amount = 35;
|
||||
|
||||
|
@ -63,8 +105,8 @@ export default function GenderGame({level, id, link}: { level: 1 | 2, id: string
|
|||
do {
|
||||
typeToUse = getRandomFromList(Object.keys(base[gender]));
|
||||
} while (!base[gender][typeToUse].length);
|
||||
const question = getRandomFromList(base[gender][typeToUse]).entry;
|
||||
base[gender][typeToUse] = base[gender][typeToUse].filter(({ entry }) => entry.ts !== question.ts);
|
||||
const question = getRandomFromList(base[gender][typeToUse]);
|
||||
base[gender][typeToUse] = base[gender][typeToUse].filter((entry) => entry.ts !== question.ts);
|
||||
yield {
|
||||
progress: makeProgress(i, amount),
|
||||
question,
|
||||
|
@ -74,7 +116,7 @@ export default function GenderGame({level, id, link}: { level: 1 | 2, id: string
|
|||
|
||||
function Display({ question, callback }: QuestionDisplayProps<T.DictionaryEntry>) {
|
||||
function check(gender: "m" | "f") {
|
||||
callback(!!question.c?.includes(gender));
|
||||
callback(!nounNotIn(gender === "m" ? mascNouns : femNouns)(question));
|
||||
}
|
||||
return <div>
|
||||
<div className="mb-4" style={{ fontSize: "larger" }}>
|
||||
|
@ -105,7 +147,7 @@ export default function GenderGame({level, id, link}: { level: 1 | 2, id: string
|
|||
questions={questions}
|
||||
id={id}
|
||||
Display={Display}
|
||||
timeLimit={level === 1 ? 55 : 70}
|
||||
timeLimit={level === 1 ? 70 : 80}
|
||||
Instructions={Instructions}
|
||||
/>
|
||||
};
|
||||
|
|
|
@ -14,12 +14,15 @@ import {
|
|||
standardizePashto,
|
||||
// pashtoConsonants,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import words from "../../words/nouns-adjs";
|
||||
import { words } from "../../words/words";
|
||||
import {
|
||||
firstVariation,
|
||||
} from "../../lib/text-tools";
|
||||
import {
|
||||
isUnisexNoun,
|
||||
} from "../../lib/type-predicates";
|
||||
|
||||
const nouns = words.filter((w) => w.category === "nouns-unisex").map(x => x.entry);
|
||||
const nouns = words.nouns.filter(isUnisexNoun);
|
||||
// type NType = "consonant" | "eyUnstressed" | "eyStressed" | "pashtun" | "withu"
|
||||
// const types: Record<NType, T.DictionaryEntry[]> = {
|
||||
// consonant: nouns.filter((w) => pashtoConsonants.includes(w.p.slice(-1))),
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
import {
|
||||
isNoun,
|
||||
isPattern1Word,
|
||||
isPattern2Word,
|
||||
isPattern3Word,
|
||||
isPattern4Word,
|
||||
isPattern5Word,
|
||||
isPattern6FemNoun,
|
||||
} from "./type-predicates";
|
||||
|
||||
/**
|
||||
* sorts a given array of on type into a typed object of arrays of subtypes, based on predicates
|
||||
* each item will only fall into one category (the first predicate it matches).
|
||||
*
|
||||
* Items that don't match any predicate are discarded
|
||||
*
|
||||
* types inforced, but it is UP TO THE USER to inforce that the type predicates do indeed determine
|
||||
* that a given item belongs in the typed arrays in X
|
||||
*
|
||||
* for example:
|
||||
*
|
||||
* ```ts
|
||||
* categorize<number, {
|
||||
* small: number[], // these could also be subtypes of number
|
||||
* big: number[],
|
||||
* }>(
|
||||
* [2,3,11],
|
||||
* {
|
||||
* small: (x: number) => x < 10,
|
||||
* big: (x: nuber) => x >= 10,
|
||||
* },
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* yields
|
||||
*
|
||||
* ```ts
|
||||
* { small: [2, 3], big: [11] }
|
||||
* ```
|
||||
*
|
||||
* @param arr
|
||||
* @param preds
|
||||
* @returns
|
||||
*/
|
||||
export function categorize<I, X extends Record<string, I[]>>(
|
||||
arr: I[],
|
||||
preds: Record<keyof X, ((item: I) => boolean) | "leftovers">
|
||||
): X {
|
||||
// 1. make the object to be returned;
|
||||
const o: X = Object.keys(preds).reduce((all, curr) => ({
|
||||
...all,
|
||||
[curr]: [],
|
||||
}), {}) as X;
|
||||
|
||||
const lk = Object.entries(preds).find(([key, entry]) => entry === "leftovers");
|
||||
const leftoverKey = lk && lk[0];
|
||||
// 2. Fill the object with the items that fit
|
||||
// go through each item in the array and add it to the category based on
|
||||
// the first predicate it matches
|
||||
arr.forEach((item) => {
|
||||
for (const p of Object.keys(preds)) {
|
||||
// @ts-ignore
|
||||
if ((preds[p] !== "leftovers") && preds[p](item)) {
|
||||
o[p].push(item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// doesn't fit a predicate, add it to the leftovers
|
||||
if (leftoverKey) {
|
||||
o[leftoverKey].push(item);
|
||||
}
|
||||
});
|
||||
|
||||
// 3. return the object of categorized items
|
||||
return o;
|
||||
}
|
||||
|
||||
export function intoPatterns(words: (Noun | Adjective)[]): {
|
||||
"pattern1": Pattern1Word[],
|
||||
"pattern2": Pattern2Word[],
|
||||
"pattern3": Pattern3Word[],
|
||||
"pattern4": Pattern4Word[],
|
||||
"pattern5": Pattern5Word[],
|
||||
"non-inflecting": NonInflecting[],
|
||||
"pattern6fem": Pattern6FemNoun[],
|
||||
} {
|
||||
return categorize<(Noun | Adjective), {
|
||||
"pattern1": Pattern1Word[],
|
||||
"pattern2": Pattern2Word[],
|
||||
"pattern3": Pattern3Word[],
|
||||
"pattern4": Pattern4Word[],
|
||||
"pattern5": Pattern5Word[],
|
||||
"non-inflecting": NonInflecting[],
|
||||
"pattern6fem": Pattern6FemNoun[],
|
||||
}>(
|
||||
words,
|
||||
{
|
||||
"pattern1": isPattern1Word,
|
||||
"pattern2": isPattern2Word,
|
||||
"pattern3": isPattern3Word,
|
||||
"pattern4": isPattern4Word,
|
||||
"pattern5": isPattern5Word,
|
||||
"pattern6fem": (n) => (isNoun(n) && isPattern6FemNoun(n)),
|
||||
"non-inflecting": "leftovers",
|
||||
},
|
||||
);
|
||||
}
|
|
@ -26,15 +26,13 @@ export type NounInput = {
|
|||
plural: boolean,
|
||||
};
|
||||
|
||||
export type AdjectiveInput = T.DictionaryEntry & { __brand: "an adjective" };
|
||||
export type ParticipleInput = T.DictionaryEntry & { __brand: "a participle" };
|
||||
export type UnisexNounInput = T.DictionaryEntry & { __brand: "a unisex noun" };
|
||||
export type SpecifiedUnisexNounInput = NounInput & { gender: T.Gender };
|
||||
export type PersonInput = T.Person;
|
||||
|
||||
export type EntityInput = SubjectInput | PredicateInput;
|
||||
export type SubjectInput = PersonInput | NounInput | ParticipleInput | SpecifiedUnisexNounInput;
|
||||
export type PredicateInput = PersonInput | NounInput | AdjectiveInput | SpecifiedUnisexNounInput | UnisexNounInput | ParticipleInput;
|
||||
export type PredicateInput = PersonInput | NounInput | Adjective | SpecifiedUnisexNounInput | UnisexNoun | ParticipleInput;
|
||||
|
||||
export function equativeMachine(sub: SubjectInput, pred: PredicateInput): EquativeMachineOutput {
|
||||
// - english equative always agrees with subject
|
||||
|
@ -128,7 +126,7 @@ function makePronoun(sub: T.Person): T.PsString[] {
|
|||
);
|
||||
}
|
||||
|
||||
function makeUnisexNoun(e: UnisexNounInput, subjPerson: T.Person | undefined): T.PsString[] {
|
||||
function makeUnisexNoun(e: UnisexNoun, subjPerson: T.Person | undefined): T.PsString[] {
|
||||
// reuse english from make noun - do the a / an sensitivity
|
||||
// if it's the predicate - get the inflection according to the subjPerson
|
||||
if (subjPerson !== undefined) {
|
||||
|
@ -199,7 +197,7 @@ function makeNoun(n: NounInput | SpecifiedUnisexNounInput, entity: "subject" | "
|
|||
return addEnglish(english, pashto);
|
||||
}
|
||||
|
||||
function makeAdjective(e: AdjectiveInput, pers: T.Person): T.PsString[] {
|
||||
function makeAdjective(e: Adjective, pers: T.Person): T.PsString[] {
|
||||
const inf = inflectWord(e);
|
||||
const english = getEnglishWord(e);
|
||||
if (!english) throw new Error("no english available for adjective");
|
||||
|
@ -303,13 +301,13 @@ export function isSpecifiedUnisexNounInput(e: EntityInput): e is SpecifiedUnisex
|
|||
return false;
|
||||
}
|
||||
|
||||
export function isUnisexNounInput(e: EntityInput): e is UnisexNounInput {
|
||||
export function isUnisexNounInput(e: EntityInput): e is UnisexNoun {
|
||||
if (isPersonInput(e)) return false;
|
||||
if ("entry" in e) return false;
|
||||
return !!e.c?.includes("unisex");
|
||||
}
|
||||
|
||||
export function isAdjectiveInput(e: EntityInput): e is AdjectiveInput {
|
||||
export function isAdjectiveInput(e: EntityInput): e is Adjective {
|
||||
if (isPersonInput(e)) return false;
|
||||
if ("entry" in e) return false;
|
||||
if (isNounInput(e)) return false;
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
// https://stackoverflow.com/a/2450976
|
||||
|
||||
function shuffleArray(array) {
|
||||
// for (let i = array.length - 1; i > 0; i--) {
|
||||
// const j = Math.floor(Math.random() * (i + 1));
|
||||
// [array[i], array[j]] = [array[j], array[i]];
|
||||
// }
|
||||
let currentIndex = array.length, temporaryValue, randomIndex;
|
||||
function shuffleArray<T>(arr: Readonly<Array<T>>): Array<T> {
|
||||
let currentIndex = arr.length, temporaryValue, randomIndex;
|
||||
|
||||
const array = [...arr];
|
||||
|
||||
// While there remain elements to shuffle...
|
||||
while (0 !== currentIndex) {
|
|
@ -1,9 +0,0 @@
|
|||
import shuffle from "./shuffle-array";
|
||||
|
||||
export const startingWord = (words, category, p) => {
|
||||
const ws = words.filter(w => w.category === category);
|
||||
return [
|
||||
ws.find(w => w.entry.p === p),
|
||||
...shuffle(ws.filter(w => w.entry.p !== p)),
|
||||
];
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import shuffle from "./shuffle-array";
|
||||
|
||||
export const startingWord = (words: Readonly<(Noun | Adjective)[]>, p: string) => {
|
||||
const firstWord = words.find(w => w.p === p);
|
||||
return [
|
||||
...firstWord ? [firstWord] : [],
|
||||
...shuffle(words.filter(w => w.p !== p)),
|
||||
];
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
import {
|
||||
pashtoConsonants,
|
||||
endsWith,
|
||||
countSyllables,
|
||||
removeAccents,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
|
||||
export function isNoun(e: Word): e is Noun {
|
||||
if ("entry" in e) return false;
|
||||
return !!(e.c && (e.c.includes("n. m.") || e.c.includes("n. f.")));
|
||||
}
|
||||
|
||||
export function isAdjective(e: Word): e is Adjective {
|
||||
if ("entry" in e) return false;
|
||||
return !!e.c?.includes("adj.") && !isNoun(e);
|
||||
}
|
||||
|
||||
export function isNounOrAdj(e: Word): e is (Noun | Adjective) {
|
||||
return isNoun(e) || isAdjective(e);
|
||||
}
|
||||
|
||||
export function isVerb(e: Word): e is Verb {
|
||||
return "entry" in e && !!e.entry.c?.startsWith("v.");
|
||||
}
|
||||
|
||||
export function isMascNoun(e: Noun | Adjective): e is MascNoun {
|
||||
return !!e.c && e.c.includes("n. m.");
|
||||
}
|
||||
|
||||
export function isFemNoun(e: Noun | Adjective): e is FemNoun {
|
||||
return !!e.c && e.c.includes("n. f.");
|
||||
}
|
||||
|
||||
export function isUnisexNoun(e: Noun | Adjective): e is UnisexNoun {
|
||||
return isNoun(e) && e.c.includes("unisex");
|
||||
}
|
||||
|
||||
export function isAdjOrUnisexNoun(e: Word): e is (Adjective | UnisexNoun) {
|
||||
return isAdjective(e) || (
|
||||
isNoun(e) && isUnisexNoun(e)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* shows if a noun/adjective has the basic (consonant / ه) inflection pattern
|
||||
*
|
||||
* @param e
|
||||
* @returns
|
||||
*/
|
||||
export function isPattern1Word(e: Noun | Adjective): e is Pattern1Word {
|
||||
if (e.noInf) return false;
|
||||
if (e.infap) return false;
|
||||
if (isFemNoun(e)) {
|
||||
return endsWith({ p: "ه", f: "a" }, e) && (endsWith({ p: pashtoConsonants }, e) && e.c.includes("anim."));
|
||||
}
|
||||
return (
|
||||
endsWith([{ p: pashtoConsonants }], e) ||
|
||||
endsWith([{ p: "ه", f: "u" }, { p: "ه", f: "h" }], e) ||
|
||||
endsWith([{ p: "ای", f: "aay" }, { p: "وی", f: "ooy" }], e)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* shows if a noun/adjective has the unstressed ی inflection pattern
|
||||
*
|
||||
* @param e
|
||||
* @returns
|
||||
*/
|
||||
export function isPattern2Word(e: Noun | Adjective): e is Pattern2Word {
|
||||
if (e.noInf) return false;
|
||||
if (e.infap) return false;
|
||||
if (isFemNoun(e)) {
|
||||
return !e.c.includes("pl.") && endsWith({ p: "ې", f: "e" }, e, true);
|
||||
}
|
||||
// TODO: check if it's a single syllable word, in which case it would be pattern 1
|
||||
return endsWith({ p: "ی", f: "ey" }, e, true)// && (countSyllables(e.f) > 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* shows if a noun/adjective has the stressed ی inflection pattern
|
||||
*
|
||||
* @param e
|
||||
* @returns
|
||||
*/
|
||||
export function isPattern3Word(e: Noun | Adjective): e is Pattern3Word {
|
||||
if (e.noInf) return false;
|
||||
if (e.infap) return false;
|
||||
if (isFemNoun(e)) {
|
||||
return endsWith({ p: "ۍ" }, e);
|
||||
}
|
||||
return (countSyllables(removeAccents(e.f)) > 1)
|
||||
? endsWith({ p: "ی", f: "éy" }, e, true)
|
||||
: endsWith({ p: "ی", f: "ey" }, e)
|
||||
}
|
||||
|
||||
/**
|
||||
* shows if a noun/adjective has the "Pashtoon" inflection pattern
|
||||
*
|
||||
* @param e
|
||||
* @returns
|
||||
*/
|
||||
export function isPattern4Word(e: Noun | Adjective): e is Pattern4Word {
|
||||
if (e.noInf) return false;
|
||||
return (
|
||||
!!(e.infap && e.infaf && e.infbp && e.infbf)
|
||||
&&
|
||||
(e.infap.slice(1).includes("ا") && e.infap.slice(-1) === "ه")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* shows if a noun/adjective has the shorter squish inflection pattern
|
||||
*
|
||||
* @param e
|
||||
* @returns
|
||||
*/
|
||||
export function isPattern5Word(e: Noun | Adjective): e is Pattern5Word {
|
||||
if (e.noInf) return false;
|
||||
return (
|
||||
!!(e.infap && e.infaf && e.infbp && e.infbf)
|
||||
&&
|
||||
(e.infaf.slice(-1) === "u")
|
||||
&&
|
||||
!e.infap.slice(1).includes("ا")
|
||||
);
|
||||
}
|
||||
|
||||
export function isPattern6FemNoun(e: Noun): e is Pattern6FemNoun {
|
||||
if (!isFemNoun(e)) return false;
|
||||
if (e.c.includes("anim.")) return false;
|
||||
return e.p.slice(-1) === "ي";
|
||||
}
|
|
@ -20,4 +20,37 @@ type GameRecord = {
|
|||
id: string,
|
||||
studyLink: string,
|
||||
Game: () => JSX.Element,
|
||||
};
|
||||
};
|
||||
|
||||
type Noun = import("@lingdocs/pashto-inflector").Types.DictionaryEntry & { c: string } & { __brand: "a noun entry" };
|
||||
type MascNoun = Noun & { __brand2: "a masc noun entry" };
|
||||
type FemNoun = Noun & { __brand2: "a fem noun entry" };
|
||||
type UnisexNoun = MascNoun & { __brand3: "a unisex noun entry" };
|
||||
type Adjective = import("@lingdocs/pashto-inflector").Types.DictionaryEntry & { c: string } & { __brand: "an adjective entry" };
|
||||
type Verb = {
|
||||
entry: import("@lingdocs/pashto-inflector").Types.DictionaryEntry & { __brand: "a verb entry" },
|
||||
complement?: import("@lingdocs/pashto-inflector").Types.DictionaryEntry,
|
||||
};
|
||||
|
||||
type RawWord = T.DictionaryEntry | {
|
||||
entry: T.DictionaryEntry,
|
||||
complement?: T.DictionaryEntry,
|
||||
};
|
||||
|
||||
// TODO: Write type predicates for these
|
||||
type Pattern1Word = (Noun | Adjective) & { __brand3: "basic inflection pattern" };
|
||||
type Pattern2Word = (Noun | Adjective) & { __brand3: "ending in unstressed ی pattern" };
|
||||
type Pattern3Word = (Noun | Adjective) & { __brand3: "ending in stressed ی pattern" };
|
||||
type Pattern4Word = (Noun | Adjective) & { __brand3: "Pashtoon pattern" };
|
||||
type Pattern5Word = (Noun | Adjective) & { __brand3: "short squish pattern" };
|
||||
type Pattern6FemNoun = FemNoun & { __brand3: "non anim. ending in ي" };
|
||||
type NonInflecting = (Noun | Adjective) & { __brand3: "non-inflecting" };
|
||||
// PLUS FEM INFLECTING
|
||||
|
||||
type Word = Noun | Adjective | Verb;
|
||||
|
||||
type Words = {
|
||||
nouns: Noun[],
|
||||
adjectives: Adjective[],
|
||||
verbs: Verb[],
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,40 @@
|
|||
import rawWords from "./raw-words";
|
||||
import {
|
||||
isAdjective,
|
||||
// isFemNoun,
|
||||
// isMascNoun,
|
||||
isNoun,
|
||||
isVerb,
|
||||
} from "../lib/type-predicates";
|
||||
import { categorize, intoPatterns } from "../lib/categorize";
|
||||
|
||||
export const words = categorize<Word, Words>(rawWords, {
|
||||
"nouns": isNoun,
|
||||
"adjectives": isAdjective,
|
||||
"verbs": isVerb,
|
||||
});
|
||||
|
||||
console.log(
|
||||
Object.entries(
|
||||
intoPatterns([
|
||||
...words.nouns,
|
||||
...words.adjectives
|
||||
])
|
||||
).reduce((ob, [key, arr]) => {
|
||||
return {
|
||||
...ob,
|
||||
[key]: arr.map(a => `${a.f} - ${a.p} - ${a.c}`),
|
||||
};
|
||||
}, {})
|
||||
);
|
||||
|
||||
// const genderedNouns = categorize<Noun, { masc: MascNoun[], fem: FemNoun[] }>(
|
||||
// words.nouns,
|
||||
// {
|
||||
// "masc": isMascNoun,
|
||||
// "fem": isFemNoun,
|
||||
// },
|
||||
// );
|
||||
|
||||
// console.log(genderedNouns);
|
||||
|
|
@ -1619,6 +1619,15 @@
|
|||
pbf "^3.2.1"
|
||||
rambda "^6.7.0"
|
||||
|
||||
"@lingdocs/pashto-inflector@^1.2.6":
|
||||
version "1.2.6"
|
||||
resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-1.2.6.tgz#41c3d8e663d4b4c1ef37bd49d7de536e05776a4d"
|
||||
integrity sha512-7FiMasn312whe9izP+E/b2Img4pUf2wzPv8Y+ZHMqtG9AhAatlKYC/IiS8PoG+EPXJ1BoSQ8UmUMao5qmu0G+w==
|
||||
dependencies:
|
||||
classnames "^2.2.6"
|
||||
pbf "^3.2.1"
|
||||
rambda "^6.7.0"
|
||||
|
||||
"@mdx-js/mdx@^0.15.5":
|
||||
version "0.15.7"
|
||||
resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-0.15.7.tgz#5fde5841d7b6f4c78f80c19fff559532af5ce5ad"
|
||||
|
|
Loading…
Reference in New Issue