Commit d988024b authored by weblate's avatar weblate

Merge branch 'translations' of...

Merge branch 'translations' of code.bitsoffreedom.nl:bitsoffreedom/mydatadoneright/frontend into translations
parents f2cc6346 c5c112b2
## Summary
## Steps to reproduce
1.
2.
## Current bug behavior?
*
## Expected correct behavior?
*
## Environment (version of operating system, browser, etc)
## Relevant logs and/or screenshots
## Possible fixes
## Is there a chance that fixing this issues has any legal consequences?
_For example, it is about changing the request we generate for the user or it is about the mentioning of a license for some dataset._
/label ~Bug ~"Needs triage"
......@@ -68,9 +68,20 @@ By default the `nl.json` is used as a fallback region, this is configured in the
"reminders_action":
"https://api.mydatadoneright.eu/api/v1/reminders/:id/:action.json"
},
"about": {
"license": {
"default": {
"title": "Something about our local version",
"title": "License information about the local database",
"markdown":
"This is the body of the section which allows markdown [Test](https://example.org)"
},
"en": {
"title": "Something about our local database translated to English",
"markdown": "The english body text"
}
},
"contact": {
"default": {
"title": "Contact us about our local version",
"markdown":
"This is the body of the section which allows markdown [Test](https://example.org)"
},
......
......@@ -8,7 +8,8 @@ interface IRegionSettings {
partners: NonNullable<MDDR.IRegion['partners']>;
address: NonNullable<MDDR.IRegion['address']>;
suggestions: NonNullable<MDDR.IRegion['suggestions']>;
about: MDDR.IRegion['about'];
contact: MDDR.IRegion['contact'];
license: MDDR.IRegion['license'];
}
interface IRegionContext {
......@@ -35,7 +36,8 @@ const getRegionSettings = (region: MDDR.Region): IRegionSettings => {
...(fallback.endpoints || {}),
...(current.endpoints || {})
},
about: current.about,
contact: current.contact,
license: current.license,
suggestions: current.suggestions || fallback.suggestions || [],
address: current.address || fallback.address || ''
};
......
import * as React from 'react';
import { v4 as uuid } from 'uuid';
import { Checkbox, Group, Input } from 'components/Form';
import withRequest, { IWithRequestProps } from 'components/Request/withRequest';
......@@ -7,6 +6,7 @@ import { Request } from 'lib/constants';
import { patchIdx } from 'lib/utils';
interface IIdentityProps extends IWithRequestProps {
id: string;
title: string;
placeholder: string;
defaultValue?: string;
......@@ -15,7 +15,7 @@ interface IIdentityProps extends IWithRequestProps {
class Identity extends React.PureComponent<IIdentityProps, never> {
onToggle = (checked: boolean) => {
const { request, title, defaultValue } = this.props;
const { id, request, title, defaultValue } = this.props;
request.set(prevRequest => {
const identifiers: Request.IIdentifier[] =
(prevRequest as Request.IAttributes).identifiers || [];
......@@ -25,7 +25,7 @@ class Identity extends React.PureComponent<IIdentityProps, never> {
identifiers: [
...identifiers,
{
id: uuid(),
id,
name: title,
value: defaultValue || ''
}
......@@ -57,17 +57,17 @@ class Identity extends React.PureComponent<IIdentityProps, never> {
};
render() {
const { title, placeholder, defaultValue, type, request } = this.props;
const { id, title, placeholder, defaultValue, type, request } = this.props;
const identifiers: Request.IIdentifier[] =
(request as Request.IAttributes).identifiers || [];
const identity = identifiers.find(identifier => identifier.name === title);
const identity = identifiers.find(identifier => identifier.id === id);
return (
<Group>
<Checkbox
checked={!!identity}
onToggle={this.onToggle}
label={title}
label={identity ? identity.name : title}
packed
/>
......
......@@ -112,6 +112,7 @@ class Identify extends React.PureComponent<IIdentifyProps, never> {
: ['email', 'customer_number'];
const presetIdentifiers = selectedIdentifiers
.map(key => ({
id: `preset.${key}`,
title: t(`request.attributes.${key}.title`, { defaultValue: key }),
value: contactDetails[key] || '',
placeholder: t(`request.attributes.${key}.placeholder`, {
......@@ -122,7 +123,7 @@ class Identify extends React.PureComponent<IIdentifyProps, never> {
const customIdentifiers = identifiers.filter(
identifier =>
presetIdentifiers.map(id => id.title).indexOf(identifier.name) === -1
presetIdentifiers.map(id => id.id).indexOf(identifier.id) === -1
);
return (
......@@ -144,10 +145,11 @@ class Identify extends React.PureComponent<IIdentifyProps, never> {
onChange={this.onChangeName}
/>
{presetIdentifiers.map(({ title, placeholder, value }) => (
{presetIdentifiers.map(({ id, title, placeholder, value }) => (
<Identity
key={title}
type="text"
id={id}
title={title}
defaultValue={value}
placeholder={placeholder}
......
......@@ -6,7 +6,7 @@ import Help from 'components/Help';
import withRequest, { IWithRequestProps } from 'components/Request/withRequest';
import { Question } from 'components/Text';
import { Request } from 'lib/constants';
import { AVAILABLE_LOCALES } from 'lib/createI18N';
import { letters } from 'lib/generateLetter';
import { ITranslatedProps } from 'lib/prop-types';
interface ILanguageProps extends IWithRequestProps, ITranslatedProps {}
......@@ -23,15 +23,17 @@ class Language extends React.PureComponent<ILanguageProps, never> {
render() {
const { t, request } = this.props;
const { language } = request;
const locales = Object.keys(AVAILABLE_LOCALES);
const { language, type } = request;
const availableLocaleCodes = Object.keys(letters).filter(
key => !!letters[key][type]
);
return (
<React.Fragment>
<Question>{t('request.step.language.title')}</Question>
<Help title=" " />
{locales.map(locale => (
{availableLocaleCodes.map(locale => (
<Group key={locale}>
<Radio
packed
......
......@@ -27,7 +27,7 @@ class Response extends React.PureComponent<IResponseProps, never> {
}
onSelect = (type: Request.CommunicationMethod) => () => {
const { t, request } = this.props;
const { request } = this.props;
request.set(prevRequest => {
const {
......@@ -39,9 +39,7 @@ class Response extends React.PureComponent<IResponseProps, never> {
// Check if we already have a type from the previous identify screen and pre-populate the email method
if (!knownEmail) {
const identity = identifiers.find(
ident => ident.name === t('request.attributes.email.title')
);
const identity = identifiers.find(ident => ident.id === 'preset.email');
if (identity && identity.value) {
knownEmail = identity.value;
......
......@@ -3,15 +3,48 @@ import * as LanguageDetector from 'i18next-browser-languagedetector';
import * as moment from 'moment-timezone';
import { reactI18nextModule } from 'react-i18next';
const AVAILABLE_LOCALES = {};
const AVAILABLE_LOCALES: {
[key: string]: { translations: any; moment?: string };
} = {};
// Attempt to load the moment locale, but fall back to the main non-variant language
// if the specified variant is not available
const loadMomentLocale = (iso: string): string | undefined => {
try {
require(`moment/locale/${iso}.js`);
return iso;
} catch (error) {
const isoParts = iso.split('-');
if (isoParts.length > 1) {
return loadMomentLocale(isoParts[0]);
}
}
return undefined;
};
// Change the currently active moment locale to the locale we were able to load when
// setting up the translations
const setMomentLocale = (iso: string) => {
const momentLocale = AVAILABLE_LOCALES[iso].moment;
if (momentLocale) {
moment.locale(momentLocale);
}
};
// Load all files available in the `locales` directory and populate the translations object
const localeFiles = require.context('locales/', true, /\.json$/);
localeFiles.keys().forEach(filename => {
const codeMatch = filename.match(/\.\/([^\.]+)/);
if (codeMatch) {
AVAILABLE_LOCALES[codeMatch[1]] = { translations: localeFiles(filename) };
if (codeMatch[1] !== 'en') {
require(`moment/locale/${codeMatch[1]}.js`);
}
const iso = codeMatch[1];
AVAILABLE_LOCALES[iso] = {
translations: localeFiles(filename),
moment: loadMomentLocale(iso.toLowerCase())
};
}
});
......@@ -32,6 +65,7 @@ export default function createI18N(region: string) {
order: ['querystring', 'cookie', 'localStorage', 'navigator']
},
fallbackLng: regionSettings.locale,
lowerCaseLng: true,
initImmediate: true,
interpolation: {
format: (value: any, format?: string, locale?: string): string => {
......@@ -56,7 +90,7 @@ export default function createI18N(region: string) {
nsMode: 'default',
wait: true
} as any,
resources: AVAILABLE_LOCALES,
resources: AVAILABLE_LOCALES as any,
whitelist: Object.keys(AVAILABLE_LOCALES)
},
() => {
......@@ -64,10 +98,8 @@ export default function createI18N(region: string) {
}
);
moment.locale(i18nInstance.language);
i18nInstance.on('languageChanged', language => {
moment.locale(language);
});
setMomentLocale(i18nInstance.language);
i18nInstance.on('languageChanged', setMomentLocale);
return i18nInstance;
}
......
......@@ -9,7 +9,7 @@ interface IHandlebarsMap {
[type: string]: (attributes: IHandlebarsAttributes) => string;
}
const letters: { [locale: string]: IHandlebarsMap } = {};
export const letters: { [locale: string]: IHandlebarsMap } = {};
const fallbackLocale = 'en';
const fallbackAttributes: Partial<Request.Attributes> = {
organization: { address: {} },
......
......@@ -3,15 +3,43 @@ import { translate } from 'react-i18next';
import Page from 'components/Page';
import Partners from 'components/Partners';
import withRegion, { IWithRegionProps } from 'components/Regional/withRegion';
import { Body, Markdown, Subtitle, TextGroup, Title } from 'components/Text';
import { IRouteProps, ITranslatedProps } from 'lib/prop-types';
import styles from './styles.scss';
interface IAboutProps extends IRouteProps<never>, ITranslatedProps {}
interface IAboutProps
extends IRouteProps<never>,
IWithRegionProps,
ITranslatedProps {}
interface ILicenseSection {
title: string;
markdown: string;
}
const isValidSection = (section: any): section is ILicenseSection => {
return (
typeof section === 'object' &&
typeof section.title === 'string' &&
typeof section.markdown === 'string'
);
};
class About extends React.PureComponent<IAboutProps, any> {
render() {
const { t } = this.props;
const { t, i18n, regionSettings } = this.props;
// Extract extra information from the region
const extra = regionSettings.license;
let license: ILicenseSection | undefined;
if (extra && isValidSection(extra.default)) {
const local = extra[i18n.language];
license = extra.default;
if (isValidSection(local)) {
license = local;
}
}
return (
<Page className={styles.about} center>
......@@ -45,6 +73,12 @@ class About extends React.PureComponent<IAboutProps, any> {
<Subtitle>{t('about.license.title')}</Subtitle>
<Markdown>{t('about.license.description')}</Markdown>
</TextGroup>
{license && (
<TextGroup>
<Subtitle>{license.title}</Subtitle>
<Markdown>{license.markdown}</Markdown>
</TextGroup>
)}
<TextGroup>
<Subtitle>{t('about.code.title')}</Subtitle>
<Markdown>{t('about.code.description')}</Markdown>
......@@ -77,4 +111,4 @@ class About extends React.PureComponent<IAboutProps, any> {
}
}
export default translate('translations')(About);
export default translate('translations')(withRegion(About));
......@@ -31,13 +31,13 @@ class Contact extends React.PureComponent<IContactProps, any> {
const { t, i18n, regionSettings } = this.props;
// Extract extra information from the region
const extra = regionSettings.about;
let about: IContactSection | undefined;
const extra = regionSettings.contact;
let contact: IContactSection | undefined;
if (extra && isValidSection(extra.default)) {
const local = extra[i18n.language];
about = extra.default;
contact = extra.default;
if (isValidSection(local)) {
about = local;
contact = local;
}
}
......@@ -48,10 +48,10 @@ class Contact extends React.PureComponent<IContactProps, any> {
<Subtitle>{t('contact.local.title')}</Subtitle>
<Markdown>{t('contact.local.description')}</Markdown>
</TextGroup>
{about && (
{contact && (
<TextGroup>
<Subtitle>{about.title}</Subtitle>
<Markdown>{about.markdown}</Markdown>
<Subtitle>{contact.title}</Subtitle>
<Markdown>{contact.markdown}</Markdown>
</TextGroup>
)}
<TextGroup>
......
......@@ -2,6 +2,7 @@ import Icon from 'components/Icon';
import * as React from 'react';
import { translate } from 'react-i18next';
import Link from 'components/Link';
import Slider, { Pagination } from 'components/Slider';
import { ITranslatedProps } from 'lib/prop-types';
import Section, { Header, Subtitle, Title } from '../Section';
......@@ -38,14 +39,9 @@ class Stages extends React.PureComponent<IStagesProps> {
<Step type="how_to_4">{t('homepage.how_to.slide4')}</Step>
<Step type="how_to_5">
{t('homepage.how_to.slide5')}
<a
className={styles.privacy}
href={t('footer.privacy_url')}
rel="noopener"
target="_blank"
>
<Link className={styles.privacy} to="/privacy">
{t('footer.privacy')}
</a>
</Link>
</Step>
</Slider>
</div>
......
......@@ -25,7 +25,6 @@
"title": "Projektpartner & Freiwillige"
},
"supporters": {
"description": "Die anfängliche Entwicklung von My Data Done Right wird unterstützt durch:",
"title": "Unterstützt von:"
}
},
......@@ -132,8 +131,7 @@
"title": "Ich bin mir nicht sicher"
},
"organization": {
"placeholder": "Tragen Sie die Organisation ein",
"title": "An wen haben Sie Ihre Anfrage gesendet?"
"placeholder": "Tragen Sie die Organisation ein"
},
"organizationAddress": {
"placeholder": "Straße 123",
......@@ -194,9 +192,6 @@
"placeholder": "1 Straße",
"title": "Adresse"
},
"submitAsRequested": {
"title": "Ich werde meine Anfrage auf die gleiche Art und Weise stellen."
},
"submittedAt": {
"placeholder": "TT-MM-JJJJ",
"title": "Datum der Anfrage"
......@@ -226,14 +221,6 @@
"title": "Was möchten Sie löschen?",
"more_information": "Du kannst darum bitten, dass alles entfernt wird. Gemäß DSGVO ist Ihr Recht auf Löschung personenbezogener Daten jedoch nicht absolut und hängt teilweise davon ab, ob Sie einen triftigen Grund haben, der in eine der in der Dropdown-Liste genannten Kategorien fällt. Zögern Sie nicht, Ihre Gründe darzulegen, indem Sie die Anfrage nach ihrer Erstellung bearbeiten."
},
"generated": {
"steps": {
"deadline": "Erwarten Sie eine Antwort innerhalb eines Monats",
"remind": "Wenn die Organisation nicht antwortet, senden Sie eine Folgeanfrage",
"send": "Senden Sie Ihre Anfrage"
},
"title": "Sie sind fast fertig. Was kommt als nächstes?"
},
"identify": {
"add": "Etwas anderes",
"description": "Was sind einige Beispiele?",
......@@ -245,8 +232,6 @@
"title": "Sprache der Anfrage"
},
"organization": {
"cancelEditing": "Zurück zur Suche",
"change": "Wählen Sie eine andere Organisation aus",
"description": "Was passiert, wenn ich unter 'Ihr Land' ein anderes Land auswähle?",
"edit_address_description": "Geben Sie die E-Mail-Adresse und Postanschrift der Organisation an",
"edit_description": "Was passiert, wenn ich 'Mit My Data Done Right teilen' aktiviere?",
......@@ -515,15 +500,12 @@
"title": "Häufig gestellte Fragen (FAQ)"
},
"footer": {
"by": "Projekt von",
"contact": "Kontakt",
"privacy": "Datenschutz",
"privacy_url": "https://www.bitsoffreedom.nl/privacy-and-terms-and-conditions/"
"privacy": "Datenschutz"
},
"form": {
"required": "Dieses Feld ist erforderlich"
},
"hello_world": "Hallo Welt!",
"homepage": {
"get_started": "Starte jetzt",
"how_to": {
......@@ -865,7 +847,7 @@
"body": "Wenn Sie eine Erinnerung für Ihre Anfrage senden möchten, können Sie ein Erinnerungsschreiben wie bei der Erstellung der ursprünglichen Anfrage erzeugen."
},
"unsubscribe": {
"action": "Fragebogen (folgt in Kürze)",
"action": "",
"meta": {
"title": "Kein Problem.",
"description": "Wir werden Ihnen keine weiteren Erinnerungen mehr schicken und Ihre E-Mail-Adresse löschen."
......@@ -874,7 +856,7 @@
"title": "Kein Problem."
},
"yes": {
"action": "Fragebogen (folgt in Kürze)",
"action": "",
"subtitle": "Wir werden Ihnen keine weiteren Erinnerungen mehr schicken und Ihre E-Mail-Adresse löschen.",
"meta": {
"title": "Schön zu hören, dass Sie eine Antwort erhalten haben!",
......
......@@ -10,6 +10,6 @@ Ich fordere Sie auf, diese personenbezogenen Daten unverzüglich zu korrigieren
{{> _identity}}
Bitte senden Sie Ihre Antwort {{#eq responseMethod "postal"}}schriftlich an die in diesem Antrag angegebene Adresse {{else}}auf gesichertem Wege per Email an {{{contactDetails.email}}}{{/eq}}. Ich freue mich auf eine Antwort innerhalb eines Monats nach Eingang meines Antrags.
{{> _response }}
{{> _footer }}
......@@ -2,7 +2,7 @@
Am {{date submittedAt "D MMMM YYYY"}} sendete ich {{{t (format "request.type.%s.reminder_title" submittedType)}}} an {{{organization.legal_name}}}{{#neq organization.legal_name organization.display_name}} (“{{{organization.display_name}}}”){{/neq}}. Bis zum heutigen Tag habe ich noch keine Antwort erhalten.
Bitte beachten Sie, dass {{{organization.short_name}}} gemäß der Datenschutz-Grundverordnung (DSGVO) zur Beantwortung und Bearbeitung meines Antrags verpflichtet ist. Gemäß der DSGVO hätte ich bis zum {{future submittedAt 1 "month" "D MMMM YYYY"}} eine Antwort erhalten sollen.
Bitte beachten Sie, dass {{{organization.display_name}}} gemäß der Datenschutz-Grundverordnung (DSGVO) zur Beantwortung und Bearbeitung meines Antrags verpflichtet ist. Gemäß der DSGVO hätte ich bis zum {{future submittedAt 1 "month" "D MMMM YYYY"}} eine Antwort erhalten sollen.
Mit dieser Erinnerung bitte ich Sie, auf meine erste Anfrage innerhalb von zwei Wochen nach Erhalt dieser Nachricht zu antworten. Bitte beachten Sie, dass ich mir alle Rechte vorbehalte, eine Beschwerde bei der Datenschutzbehörde einzureichen oder alle anderen im Rahmen der DSGVO vorgesehenen Maßnahmen zur Durchsetzung meiner Rechte zu ergreifen.
......
......@@ -25,7 +25,6 @@
"title": "Συνεργάτες Έργου & Εθελοντές"
},
"supporters": {
"description": "Η αρχική ανάπτυξη του My Data Done Right υποστηρίζεται από:",
"title": "Αρχική επιδότηση από:"
}
},
......@@ -292,10 +291,8 @@
},
"footer": {
"about": "Σχετικά με εμάς",
"by": "Ένα εγχείρημα του",
"contact": "Επικοινωνία",
"privacy": "Πολιτική Απορρήτου",
"privacy_url": "https://www.bitsoffreedom.nl/privacy-and-terms-and-conditions/"
"privacy": "Πολιτική Απορρήτου"
},
"form": {
"required": "Αυτό το πεδίο είναι απαραίτητο"
......@@ -647,7 +644,7 @@
"title": "Κανένα πρόβλημα."
},
"unsubscribe": {
"action": "Ερωτηματολόγιο (σύντομα)",
"action": "",
"action_url": "",
"body": "",
"meta": {
......@@ -658,7 +655,7 @@
"title": "Κανένα πρόβλημα."
},
"yes": {
"action": "Ερωτηματολόγιο (σύντομα)",
"action": "",
"action_url": "",
"body": "",
"meta": {
......@@ -742,8 +739,7 @@
"title": "Δεν είμαι σίγουρος"
},
"organization": {
"placeholder": "Πληκτρολογήστε τον οργανισμό",
"title": "Σε ποιον στείλατε το αίτημά σας;"
"placeholder": "Πληκτρολογήστε τον οργανισμό"
},
"organizationAddress": {
"placeholder": "Οδός 123",
......@@ -836,14 +832,6 @@
"more_information": "Μπορείτε να ζητήσετε να διαγραφεί το οτιδήποτε. Ωστόσο, σύμφωνα με τον ΓΚΠΔ το δικαίωμά σας να διαγράψετε προσωπικά δεδομένα δεν είναι απόλυτο και εν μέρει εξαρτάται από το αν έχετε έγκυρο λόγο, ο οποίος εμπίπτει σε μία από τις κατηγορίες που αναφέρονται στην αναπτυσσόμενη λίστα. Μπορείτε να εξηγήσετε τους λόγους σας επεξεργαζόμενοι το αίτημα αφού δημιουργηθεί.",
"title": "Τι θέλετε να διαγράψετε;"
},
"generated": {
"steps": {
"deadline": "Αναμείνετε μια απάντηση μέσα σε ένα μήνα",
"remind": "Εάν ο οργανισμός δεν απαντήσει, στείλτε ένα συμπληρωματικό μήνυμα",
"send": "Στείλτε το αίτημά σας"
},
"title": "Έχετε σχεδόν τελειώσει. Τι ακολουθεί;"
},
"identify": {
"add": "Κάτι άλλο",
"description": "Ποια είναι τα παραδείγματα;",
......@@ -855,8 +843,6 @@
"title": "Ζητήστε Γλώσσα"
},
"organization": {
"cancelEditing": "Επιστροφή στην αναζήτηση",
"change": "Επιλέξτε έναν άλλο οργανισμό",
"description": "Τι θα συμβεί αν επιλέξω μια διαφορετική χώρα κάτω από τη λέξη \"Η χώρα σας\";",
"edit_address_description": "Καταχωρίστε τη διεύθυνση ηλεκτρονικού ταχυδρομείου και την ταχυδρομική διεύθυνση του οργανισμού",
"edit_address_more_information": "",
......
......@@ -10,6 +10,6 @@
{{> _identity}}
Παρακαλώ απαντήστε μου {{#eq responseMethod "postal"}}γραπτώς στη διεύθυνση που αναφέρεται στο παρόν αίτημα {{else}}με ασφαλή μέσα μέσω ηλεκτρονικού ταχυδρομείου στη διεύθυνση {{{contactDetails.email}}}{{/eq}}. Παρακαλώ να λάβω απάντηση εντός ενός μηνός από την παραλαβή του αιτήματός μου.
{{> _response }}
{{> _footer }}
......@@ -2,7 +2,7 @@
Στις {{date submittedAt "D MMMM YYYY"}} έστειλα ένα {{{t (format "request.type.%s.reminder_title" submittedType)}}} στον οργανισμό {{{organization.legal_name}}}{{#neq organization.legal_name organization.display_name}} (“{{{organization.display_name}}}”){{/neq}}. Μέχρι σήμερα δεν έχω λάβει κάποια απάντηση.
Παρακαλώ να σημειωθεί πως ο οργανισμός {{{organization.short_name}}} υποχρεούται να μου απαντήσει και να διεκπεραιώσει το αίτημα μου όπως ορίζει ο ΓΚΠΔ. Σύμφωνα με τον Κανονισμό, θα έπρεπε να έχω λάβει μια απάντηση μέχρι τις {{future submittedAt 1 "month" "D MMMM YYYY"}}.
Παρακαλώ να σημειωθεί πως ο οργανισμός {{{organization.display_name}}} υποχρεούται να μου απαντήσει και να διεκπεραιώσει το αίτημα μου όπως ορίζει ο ΓΚΠΔ. Σύμφωνα με τον Κανονισμό, θα έπρεπε να έχω λάβει μια απάντηση μέχρι τις {{future submittedAt 1 "month" "D MMMM YYYY"}}.
Με αυτή την υπενθύμιση παρακαλώ να ανταποκριθείτε στο αρχικό μου αίτημα εντός δύο (2) εβδομάδων από την λήψη αυτής της υπενθύμισης. Υπενθυμίζω πως διατηρώ κάθε δικαίωμα να υποβάλω καταγγελία ενώπιον της αρμόδιας Αρχής Προστασίας Δεδομένων Προσωπικού Χαρακτήρα ή να ακολουθήσω τις υπόλοιπες νομικές οδούς που προβλέπει ο ΓΚΠΔ για να ασκήσω τα δικαιώματα μου.
......
......@@ -25,7 +25,6 @@
"title": "Project Partners & Volunteers"