From ab5173cf59e03bf38531779f35c2c42b6174a02e Mon Sep 17 00:00:00 2001 From: Andrea Vos Date: Tue, 17 Nov 2020 19:21:49 +0100 Subject: [PATCH 1/8] =?UTF-8?q?#103=20[incl][pl]=20s=C5=82ownik=20j=C4=99z?= =?UTF-8?q?yka=20inkluzywnego=20-=20kod?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/InclusiveDictionary.vue | 256 +++++++++++++++++++++++++++++ components/InclusiveSubmitForm.vue | 109 ++++++++++++ components/NounSubmitForm.vue | 1 - locale/pl/nouns/NounsExtra.vue | 24 +++ locale/pl/nouns/NounsNav.vue | 41 +++-- locale/pl/translations.suml | 17 ++ migrations/006-inclusive.sql | 15 ++ server/index.js | 1 + server/routes/inclusive.js | 102 ++++++++++++ server/routes/nouns.js | 5 +- src/classes.js | 27 +++ src/helpers.js | 4 + 12 files changed, 584 insertions(+), 18 deletions(-) create mode 100644 components/InclusiveDictionary.vue create mode 100644 components/InclusiveSubmitForm.vue create mode 100644 migrations/006-inclusive.sql create mode 100644 server/routes/inclusive.js diff --git a/components/InclusiveDictionary.vue b/components/InclusiveDictionary.vue new file mode 100644 index 000000000..40e0523c6 --- /dev/null +++ b/components/InclusiveDictionary.vue @@ -0,0 +1,256 @@ + + + + + diff --git a/components/InclusiveSubmitForm.vue b/components/InclusiveSubmitForm.vue new file mode 100644 index 000000000..59deb6042 --- /dev/null +++ b/components/InclusiveSubmitForm.vue @@ -0,0 +1,109 @@ + + + diff --git a/components/NounSubmitForm.vue b/components/NounSubmitForm.vue index 0a40394bb..1bee23791 100644 --- a/components/NounSubmitForm.vue +++ b/components/NounSubmitForm.vue @@ -32,7 +32,6 @@ nouns.neuter nouns.neuterShort - diff --git a/locale/pl/nouns/NounsExtra.vue b/locale/pl/nouns/NounsExtra.vue index 4cc421fb0..6e7934f02 100644 --- a/locale/pl/nouns/NounsExtra.vue +++ b/locale/pl/nouns/NounsExtra.vue @@ -3,6 +3,7 @@

+ nouns.neuterNouns.header

@@ -66,6 +67,7 @@

+ nouns.dukajNouns.header

@@ -150,6 +152,7 @@

+ nouns.personNouns.header

@@ -209,6 +212,27 @@ + + + +

+ + nouns.inclusive.headerLong +

+ + nouns.inclusive.info + +
+ +

+ + nouns.inclusive.headerLong +

+
+
+ +
+
diff --git a/locale/pl/nouns/NounsNav.vue b/locale/pl/nouns/NounsNav.vue index fa18d5085..5f08ceea0 100644 --- a/locale/pl/nouns/NounsNav.vue +++ b/locale/pl/nouns/NounsNav.vue @@ -1,16 +1,31 @@ + + diff --git a/locale/pl/translations.suml b/locale/pl/translations.suml index 3ae86346a..1aa95ebef 100644 --- a/locale/pl/translations.suml +++ b/locale/pl/translations.suml @@ -225,6 +225,23 @@ nouns: plural: 'liczba mnoga' pluralShort: 'l. mn.' + inclusive: + header: 'Inkluzywność' + headerLong: 'Słownik inkluzywnego języka' + id: 'inkluzywnosc' + insteadOf: 'Zamiast' + say: 'Lepiej mów' + because: 'Ponieważ' + info: + - > + Język jest nośnikiem myśli, nośnikiem kultury, podstawą komunikacji. Wpływa na to, co robimy i jak myślimy. + Jeśli chcemy tworzyć społeczeństwo otwarte na różnorodność i akceptujące odmienność, + to nasz język też musi być włączający. + - > + Inkluzywny język to nie tylko rzeczowniki i nie tylko kwestie płciowości. + Poniżej przedstawiamy słownik, w którym zbieramy sugestie, + jakich konstrukcji lepiej unikać i dlatego, oraz czym je zastępować. + names: header: 'Imiona' headerLong: 'Neutralne imiona' diff --git a/migrations/006-inclusive.sql b/migrations/006-inclusive.sql new file mode 100644 index 000000000..3119aa9e0 --- /dev/null +++ b/migrations/006-inclusive.sql @@ -0,0 +1,15 @@ +-- Up + +CREATE TABLE inclusive ( + id TEXT NOT NULL PRIMARY KEY, + insteadOf TEXT NOT NULL, + say TEXT NOT NULL, + because TEXT NOT NULL, + locale TEXT NOT NULL, + approved INTEGER NOT NULL, + base_id TEXT +); + +-- Down + +DROP TABLE inclusive; diff --git a/server/index.js b/server/index.js index f67126e03..06321565f 100644 --- a/server/index.js +++ b/server/index.js @@ -39,6 +39,7 @@ app.use(require('./routes/admin').default); app.use(require('./routes/pronouns').default); app.use(require('./routes/sources').default); app.use(require('./routes/nouns').default); +app.use(require('./routes/inclusive').default); export default { path: '/api', diff --git a/server/routes/inclusive.js b/server/routes/inclusive.js new file mode 100644 index 000000000..5bf3e9385 --- /dev/null +++ b/server/routes/inclusive.js @@ -0,0 +1,102 @@ +import { Router } from 'express'; +import SQL from 'sql-template-strings'; +import {ulid} from "ulid"; +import {isTroll} from "../../src/helpers"; + +const approve = async (db, id) => { + const { base_id } = await db.get(SQL`SELECT base_id FROM inclusive WHERE id=${id}`); + if (base_id) { + await db.get(SQL` + DELETE FROM inclusive + WHERE id = ${base_id} + `); + } + await db.get(SQL` + UPDATE inclusive + SET approved = 1, base_id = NULL + WHERE id = ${id} + `); +} + +const router = Router(); + +router.get('/inclusive', async (req, res) => { + return res.json(await req.db.all(SQL` + SELECT * FROM inclusive + WHERE locale = ${req.config.locale} + AND approved >= ${req.admin ? 0 : 1} + ORDER BY approved, insteadOf + `)); +}); + +router.get('/inclusive/search/:term', async (req, res) => { + const term = '%' + req.params.term + '%'; + return res.json(await req.db.all(SQL` + SELECT * FROM inclusive + WHERE locale = ${req.config.locale} + AND approved >= ${req.admin ? 0 : 1} + AND (insteadOf like ${term} OR say like ${term}) + ORDER BY approved, insteadOf + `)); +}); + +router.post('/inclusive/submit', async (req, res) => { + if (!(req.user && req.user.admin) && isTroll(JSON.stringify(req.body))) { + return res.json('ok'); + } + + const id = ulid(); + await req.db.get(SQL` + INSERT INTO inclusive (id, insteadOf, say, because, approved, base_id, locale) + VALUES ( + ${id}, + ${req.body.insteadOf.join('|')}, ${req.body.say.join('|')}, ${req.body.because}, + 0, ${req.body.base}, ${req.config.locale} + ) + `); + + if (req.admin) { + await approve(req.db, id); + } + + return res.json('ok'); +}); + +router.post('/inclusive/hide/:id', async (req, res) => { + if (!req.admin) { + res.status(401).json({error: 'Unauthorised'}); + } + + await req.db.get(SQL` + UPDATE inclusive + SET approved = 0 + WHERE id = ${req.params.id} + `); + + return res.json('ok'); +}); + +router.post('/inclusive/approve/:id', async (req, res) => { + if (!req.admin) { + res.status(401).json({error: 'Unauthorised'}); + } + + await approve(req.db, req.params.id); + + return res.json('ok'); +}); + +router.post('/inclusive/remove/:id', async (req, res) => { + if (!req.admin) { + res.status(401).json({error: 'Unauthorised'}); + } + + await req.db.get(SQL` + DELETE FROM inclusive + WHERE id = ${req.params.id} + `); + + return res.json('ok'); +}); + +export default router; diff --git a/server/routes/nouns.js b/server/routes/nouns.js index 442500e1d..8dd1cd583 100644 --- a/server/routes/nouns.js +++ b/server/routes/nouns.js @@ -1,10 +1,7 @@ import { Router } from 'express'; import SQL from 'sql-template-strings'; import {ulid} from "ulid"; - -const isTroll = (body) => { - return ['cipeusz', 'feminazi', 'bruksela', 'zboczeń'].some(t => body.indexOf(t) > -1); -} +import {isTroll} from "../../src/helpers"; const approve = async (db, id) => { const { base_id } = await db.get(SQL`SELECT base_id FROM nouns WHERE id=${id}`); diff --git a/src/classes.js b/src/classes.js index f78059705..9dbabde5e 100644 --- a/src/classes.js +++ b/src/classes.js @@ -433,6 +433,33 @@ export class NounDeclension { } +export class InclusiveEntry { + constructor({id, insteadOf, say, because, approved = true, base_id = null}) { + this.id = id; + this.insteadOf = insteadOf.split('|'); + this.say = say.split('|'); + this.because = because; + this.approved = !!approved; + this.base = base_id; + } + + matches(filter) { + if (!filter) { + return true; + } + + for (let field of ['insteadOf', 'say']) { + for (let value of this[field]) { + if (value.toLowerCase().indexOf(filter.toLowerCase()) > -1) { + return true; + } + } + } + return false; + } +} + + export class Name { constructor(name, origin, meaning, usage, legally, pros, cons, notablePeople, count, links) { this.name = name; diff --git a/src/helpers.js b/src/helpers.js index e1820dac5..1fdbb4fad 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -131,3 +131,7 @@ export const now = function () { export const isEmoji = char => { return !!char.match(/^(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])$/) } + +export const isTroll = (body) => { + return ['cipeusz', 'feminazi', 'bruksela', 'zboczeń'].some(t => body.indexOf(t) > -1); +} From b93bf4db46bbc76362dd6e90ddc952f33c457877 Mon Sep 17 00:00:00 2001 From: Andrea Vos Date: Tue, 17 Nov 2020 19:37:32 +0100 Subject: [PATCH 2/8] =?UTF-8?q?#103=20[incl][pl]=20s=C5=82ownik=20j=C4=99z?= =?UTF-8?q?yka=20inkluzywnego=20-=20notify?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/notify.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/server/notify.js b/server/notify.js index 6cd5b804e..3d9808eac 100644 --- a/server/notify.js +++ b/server/notify.js @@ -5,15 +5,20 @@ const mailer = require('../src/mailer'); async function notify() { const db = await dbConnection(); - const awaitingModeration = (await db.all(`SELECT locale, count(*) as c FROM nouns WHERE approved = 0 GROUP BY locale`)); + const awaitingModeration = [ + ...(await db.all(`SELECT 'nouns' as type, locale, count(*) as c FROM nouns WHERE approved = 0 GROUP BY locale`)), + ...(await db.all(`SELECT 'inclusive' as type, locale, count(*) as c FROM inclusive WHERE approved = 0 GROUP BY locale`)), + ]; if (!awaitingModeration.length) { console.log('No entries awaiting moderation'); return; } const awaitingModerationGrouped = {} + let count = 0; for (let m of awaitingModeration) { - awaitingModerationGrouped[m.locale] = m.c; + awaitingModerationGrouped[m.type + '-' + m.locale] = m.c; + count += m.c; } console.log('Entries awaiting moderation: ', awaitingModerationGrouped); @@ -24,8 +29,8 @@ async function notify() { console.log('Sending email to ' + email) mailer( email, - '[Pronouns] Dictionary entries awaiting moderation: ' + JSON.stringify(awaitingModerationGrouped), - JSON.stringify(awaitingModerationGrouped) + '[Pronouns] Dictionary entries awaiting moderation: ' + count, + 'Dictionary entries awaiting moderation: \n' + JSON.stringify(awaitingModerationGrouped, null, 4), ); } } From 3583610997eb3d336afca52161b9df555b6ec22f Mon Sep 17 00:00:00 2001 From: Andrea Vos Date: Wed, 18 Nov 2020 18:46:44 +0100 Subject: [PATCH 3/8] [pl] english - update typics link --- locale/pl/config.suml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locale/pl/config.suml b/locale/pl/config.suml index eaa8d2efb..99e15852d 100644 --- a/locale/pl/config.suml +++ b/locale/pl/config.suml @@ -186,9 +186,9 @@ english: - > The thing is, historically this grammatical gender has only been applied to inanimate objects, pets and kids (in a very limited way). - To apply it for adults sounds dehumanizing for most {https://avris.it/blog/queers-typics=typic} listeners + To apply it for adults sounds dehumanizing for most {https://avris.it/blog/queer-typic=typic} listeners and using the neuter forms of verbs in the 1st and 2nd person - is so rare that {https://avris.it/blog/queers-typics=typic} listeners (falsely) think that it's incorrect. + is so rare that listeners (falsely) think that it's incorrect. Eg. one could say „dziecko zrobiło” (=“the kid has done”), but the kid themselves would use the forms corresponding to their assigned gender: „zrobiłem”/„zrobiłam” (=“I have done”), From c6f31668fa08afb264a3540b82c581d98691a9fd Mon Sep 17 00:00:00 2001 From: Andrea Vos Date: Wed, 18 Nov 2020 18:55:29 +0100 Subject: [PATCH 4/8] [en][links] apa guidelines --- locale/en/config.suml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/locale/en/config.suml b/locale/en/config.suml index ce931d40b..287b58f15 100644 --- a/locale/en/config.suml +++ b/locale/en/config.suml @@ -81,6 +81,11 @@ links: icon: 'book-open' url: 'https://www.merriam-webster.com/words-at-play/singular-nonbinary-they' headline: 'Merriam Webster on the singular “they” as a nonbinary pronoun' + - + icon: 'book-open' + url: 'https://apastyle.apa.org/style-grammar-guidelines/grammar/singular-they' + headline: 'Style and grammar guidelines regarding singular “they”' + extra: '– American Psychological Association' - icon: 'wikipedia-w' iconSet: 'b' From e77ad40a04b1b029e10ea137cb6343074202c626 Mon Sep 17 00:00:00 2001 From: Andrea Vos Date: Sun, 22 Nov 2020 20:53:49 +0100 Subject: [PATCH 5/8] #109 [support] ko-fi --- locale/en/config.suml | 7 +++---- locale/en/translations.suml | 2 +- locale/pl/config.suml | 7 +++---- locale/pl/translations.suml | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/locale/en/config.suml b/locale/en/config.suml index 287b58f15..07385f0a6 100644 --- a/locale/en/config.suml +++ b/locale/en/config.suml @@ -152,10 +152,9 @@ support: enabled: true links: - - icon: 'paypal' - iconSet: 'b' - url: 'https://paypal.me/AndreAvris' - headline: 'PayPal' + icon: 'coffee' + url: 'https://ko-fi.com/radajezykaneutralnego' + headline: 'Ko-Fi' user: enabled: true diff --git a/locale/en/translations.suml b/locale/en/translations.suml index 67e53d144..e60e2e8ac 100644 --- a/locale/en/translations.suml +++ b/locale/en/translations.suml @@ -304,7 +304,7 @@ support: header: 'Support' description: > If you want to chip in for the server, domains, stickers etc., or simply buy the authors a beer, - you can use the link below (please mention „pronouns.page” in the transaction description): + you can use the link below: user: header: 'Account' diff --git a/locale/pl/config.suml b/locale/pl/config.suml index 99e15852d..116c0f1cf 100644 --- a/locale/pl/config.suml +++ b/locale/pl/config.suml @@ -305,10 +305,9 @@ support: enabled: true links: - - icon: 'paypal' - iconSet: 'b' - url: 'https://paypal.me/AndreAvris' - headline: 'PayPal' + icon: 'coffee' + url: 'https://ko-fi.com/radajezykaneutralnego' + headline: 'Ko-Fi' user: enabled: true diff --git a/locale/pl/translations.suml b/locale/pl/translations.suml index 1aa95ebef..edf037bd2 100644 --- a/locale/pl/translations.suml +++ b/locale/pl/translations.suml @@ -696,7 +696,7 @@ support: header: 'Wsparcie' description: > Jeśli chcesz się zrzucić na serwer, domeny, wlepki itp., lub zwyczajnie postawić autorzom piwo, - możesz skorzystać z poniższego linku (wspomnij o „zaimki.pl” w opisie transakcji): + możesz skorzystać z poniższego linku: user: header: 'Konto' From 3bed2f13417f826b807f7bd026a367ffbf056858 Mon Sep 17 00:00:00 2001 From: Andrea Vos Date: Sun, 22 Nov 2020 21:08:00 +0100 Subject: [PATCH 6/8] #106 [pl] kolektyw --- components/Authors.vue | 15 ++++++++++++--- ...czas-sie-obnosic.md => czas-się-obnosić.md} | 0 locale/pl/blog/rada-języka-neutralnego.md | 17 +++++++++++++++++ locale/pl/config.suml | 5 ++++- static/img/łoś.jpg | Bin 0 -> 29726 bytes 5 files changed, 33 insertions(+), 4 deletions(-) rename locale/pl/blog/{czas-sie-obnosic.md => czas-się-obnosić.md} (100%) create mode 100644 locale/pl/blog/rada-języka-neutralnego.md create mode 100644 static/img/łoś.jpg diff --git a/components/Authors.vue b/components/Authors.vue index c2d77ce8a..09c2d5e10 100644 --- a/components/Authors.vue +++ b/components/Authors.vue @@ -1,9 +1,18 @@