Both sides previous revision
Poprzednia wersja
Nowa wersja
|
Poprzednia wersja
|
pl:dydaktyka:ztb:2010:projekty:thankswithbeer:start [2010/04/07 14:11] ztb2010 przywrócono poprzednią wersję |
pl:dydaktyka:ztb:2010:projekty:thankswithbeer:start [2019/06/27 15:50] (aktualna) |
==== Streszczenie ==== | ==== Streszczenie ==== |
| |
Projekt stanowi podstawę bazodanową dla projektu realizowanego z przedmiotu Technologie i Programowanie WWW. | Projekt stanowi podstawę bazodanową dla projektu realizowanego z przedmiotu Technologie i Programowanie WWW. |
| |
==== Sformułowanie zadania projektowego ==== | ==== Sformułowanie zadania projektowego ==== |
| |
==== Określenie scenariuszy użycia ==== | ==== Określenie scenariuszy użycia ==== |
| |
| - **Scenariusze dla Użytkownika\Gościa**: |
| - Dostęp do serwisu: |
| * Rejestracja w systemie |
| - Wyszukiwanie użytkowników: |
| * Wyszukanie użytkownika |
| * Przeglądnie profilu użytkownika |
| - Przeglądanie treści serwisu: |
| * Wyszukanie informacji na temat piwa |
| - Główne funkcjonalności: |
| * 'Postawienie' piwa za pozytywny wpis na forum |
| - **Scenariusze dla Użytkownika**: |
| - Dostęp do serwisu: |
| * Zalogowanie do systemu |
| * Przypomnienie hasła użytkownika |
| - Zarządzanie kontem: |
| * Zmiana danych użytkownika |
| * Zmiana grafiki do podpisów |
| * Pobranie kodu do podpisu |
| * Dodawanie/edycja lubianych/nielubianych piw |
| - **Scenariusze dla Administratora**: |
| - Zarządzanie użytkownikami: |
| * Zmiana danych (statusu) użytkownika |
| * Usuwanie użytkownika |
| - Zarządzanie grupami i uprawnieniami: |
| * Zmiana uprawnień grupy |
| * Zmiana uprawnień użytkownika |
| - Zarządzanie treścią serwisu: |
| * Moderacja biblioteki piw |
| |
| === Rozwinięcie przykładowych scenariuszy użycia === |
| == Podziękuj piwem == |
| |
| Nazwa | Podziękuj piwem | | | Nazwa | Podziękuj piwem | |
| Twórca | Michał Paszko | | | Twórca | Michał Paszko | |
| Poziom ważności | Średni | | | Poziom ważności | Ważny | |
| Typ przypadku użycia | Ogólny | | | Typ przypadku użycia | Ogólny | |
| Aktorzy | Autor wpisu, Adresat wpisu | | | Aktorzy | Autor wpisu, Adresat wpisu | |
| Główny przepływ zdarzeń | 1. Czytelnik (adresat) wpisu postanawia podziekowac autorowi wirtualnym piwem. 2. Po kliknieciu na link z podziekowaniem adresat jest przekierowywany na strone profilu autora wpisu w serwisie thankswithbeer.com. 3. Adresat wpisu wybiera rodzaj oraz ilosc wirtualnego piwa, którym chce obdarowac autora wpisu. 4. Statystyki autora wpisu zostaja zaktualizowane w serwisie thankswithbeer.com. | | | Główny przepływ zdarzeń | 1. Czytelnik (adresat) wpisu postanawia podziekowac autorowi wirtualnym piwem. 2. Po kliknieciu na link z podziekowaniem adresat jest przekierowywany na strone profilu autora wpisu w serwisie thankswithbeer.com. 3. Adresat wpisu wybiera rodzaj oraz ilosc wirtualnego piwa, którym chce obdarowac autora wpisu. 4. Statystyki autora wpisu zostaja zaktualizowane w serwisie thankswithbeer.com. | |
| Alternatywne przepływy zdarzeń | 2a. Adresat wpisu zakłada konto w serwisie thankswithbeer.com 2b. Adresat wpisu - jesli posiada aktywne konto w serwisie thankswithbeer.com - loguje sie do serwisu. 3a. Adresat wpisu dodaje komentarz podziekowania. 4a. Statystyki adresata wpisu - jesli posiada aktywne konto w serwisie thankswithbeer.com zostaja zaktualizowane. | | | Alternatywne przepływy zdarzeń | 2a. Adresat wpisu zakłada konto w serwisie thankswithbeer.com 2b. Adresat wpisu - jesli posiada aktywne konto w serwisie thankswithbeer.com - loguje sie do serwisu. 3a. Adresat wpisu dodaje komentarz podziekowania. 4a. Statystyki adresata wpisu - jesli posiada aktywne konto w serwisie thankswithbeer.com zostaja zaktualizowane. | |
| |
| {{:pl:dydaktyka:ztb:2010:projekty:thankswithbeer:use_case_model.png}} |
| |
| |
| == Moderacja wpisów == |
| |
| | Nazwa | Moderacja wpisów | |
| | Twórca | Leszek Piątek | |
| | Poziom ważności | Mało ważny | |
| | Typ przypadku użycia | Ogólny | |
| | Aktorzy | Autor moderowanego wpisu, Moderator | |
| | Krótki opis | Autor moderowanego wpisu, dodaje nowy wpis (piwo, browar, grupę browarniczą), który później jest akceptowany lub nie przez moderatora. | |
| | Warunki wstępne | Moderator posiada odpowiedni typ konta w naszym serwisie. Autor moderowanego wpisu wchodzi na odpowiednią stronę dodawania wpisów. | |
| | Warunki końcowe | Wpis dokonany przez Autora moderowanego wpisu jest akceptowany lub też nie przez administratora. | |
| | Główny przepływ zdarzeń | 1. Autor moderowanego wpisu Dodaje wpis. 2. Wpis jest dodawany i zapisywany w bazie. Wpis oznaczany jest jako do moderacji - nie wyświetla się innym użytkownikom. 3. Moderator loguje się do systemu i przechodzi na panel moderacji, gdzie ma listę wpisów. 4. Moderator przechodzi na każdy wpis i dokonuje weryfikacji poprawności wpisów. 5. Moderator akceptuje wpis - wpis staje się widoczny dla wszystkich. 6. Moderator usuwa wpis - usuwamy wpis z bazy. | |
| |
| {{:pl:dydaktyka:ztb:2010:projekty:thankswithbeer:use_case_model2.jpg}} |
| |
==== Identyfikacja funkcji ==== | ==== Identyfikacja funkcji ==== |
| |
| * Logowanie/wylogowywanie z serwisu |
| * Dodanie/usunięcie/modyfikacja konta użytkownika |
| * Dodanie/usunięcie/modyfikacja informacji o piwach oraz browarach |
| * Wybór preferowanych oraz nie preferowanych piw przez użytkownika |
| * Modyfikacja //stanu zapiwienia// użytkownika |
| * Generowanie elementu graficznego przedstawiającego //stan zapiwienia// |
| * Moderacja (uprawnienie dla użytkownika) wpisów w bibliotece piwa |
| * Stworzenie unikalnego linku do profilu piwosza |
| * *Polecanie piw innym użytkownikom |
| * *Sugerowanie piw innych piwoszy na podstawie statystyk użytkowników |
| * *Stawianie //mniej wirtualnego piwa// w postaci wpłat na konto PayPal |
| * Backup bazy danych |
| |
| * - funkcjonalności o mniejszym priorytecie |
| |
| |
==== Analiza hierarchii funkcji projektowanej aplikacji ==== | ==== Analiza hierarchii funkcji projektowanej aplikacji ==== |
| |
| === FHD - Functional Hierarchy Diagram === |
| |
| {{:pl:dydaktyka:ztb:2010:projekty:thankswithbeer:diagram_fhd.png}} |
| |
==== Budowa i analiza diagramu przepływu danych ==== | ==== Budowa i analiza diagramu przepływu danych ==== |
| |
| === Diagram kontekstowy === |
| |
| {{:pl:dydaktyka:ztb:2010:projekty:thankswithbeer:twb_dfd_kontekstowy.png|}} |
| |
| === Diagram główny === |
| |
| {{:pl:dydaktyka:ztb:2010:projekty:thankswithbeer:twb_dfd_glowny.png|}} |
| |
| === 1. Obsługa użytkownika niezarejestrowanego === |
| |
| {{:pl:dydaktyka:ztb:2010:projekty:thankswithbeer:twb_dfd_1.png|}} |
| |
| == 1.8 Postawienie piwa == |
| |
| {{:pl:dydaktyka:ztb:2010:projekty:thankswithbeer:twb_dfd_1_8.png|}} |
| |
| === 2. Obsługa użytkownika zarejestrowanego === |
| |
| {{:pl:dydaktyka:ztb:2010:projekty:thankswithbeer:twb_dfd_2.png|}} |
| |
| == 2.4 Zarządzanie kontem == |
| |
| {{:pl:dydaktyka:ztb:2010:projekty:thankswithbeer:twb_dfd_2_4.png|}} |
| |
| == 2.5 Polecanie piw użytkownikom == |
| |
| {{:pl:dydaktyka:ztb:2010:projekty:thankswithbeer:twb_dfd_2_5.png|}} |
| |
| == 2.6 Moderacja bazy piw == |
| |
| {{:pl:dydaktyka:ztb:2010:projekty:thankswithbeer:twb_dfd_2_6.png|}} |
| |
| === 3. Obsługa administratora === |
| |
| {{:pl:dydaktyka:ztb:2010:projekty:thankswithbeer:twb_dfd_3.png|}} |
| |
| == 3.4 Zarządzanie użytkownikami == |
| |
| {{:pl:dydaktyka:ztb:2010:projekty:thankswithbeer:twb_dfd_3_4.png|}} |
| |
| == 3.5 Zarządzanie grupami i uprawnieniami == |
| |
| {{:pl:dydaktyka:ztb:2010:projekty:thankswithbeer:twb_dfd_3_5.png|}} |
| |
| == 3.6 Moderacja biblioteki piw == |
| |
| {{:pl:dydaktyka:ztb:2010:projekty:thankswithbeer:twb_dfd_3_6.png|}} |
| |
| |
==== Wybór encji (obiektów) i ich atrybutów ==== | ==== Wybór encji (obiektów) i ich atrybutów ==== |
| |
| **BEER** |
| * name (VARCHAR) |
| * desc* (TEXT) |
| * status* (boolean, czy piwo zaakceptowane w danym języku czy nie) |
| * brewery (FK, BREWERY) |
| * price_eur (Float(10)) - cena za 0,5 |
| * gallery (FK, GALLERY) |
| |
| **LOVEDBEERS** |
| * user (FK, USER) |
| * beer (FK, BEER) |
| * rating (FK, LOVEDBEERRATING) |
| |
| **HATEDBEERS** |
| * user (FK, USER) |
| * beer (FK, BEER) |
| * rating (FK, HATEDBEERRATING) |
| |
| **LOVEDBEERRATING** |
| * name * (VARCHAR) |
| * value (int(2)) |
| |
| **HATEDBEERRATING** |
| * name * (VARCHAR) |
| * value (int(2)) |
| |
| **BREWERY** |
| * name (VARCHAR 150) |
| * desc* (TEXT) |
| * status* (boolean, czy browar zaakceptowane w danym języku czy nie) |
| * brewery_owner (FK, BREWERY_OWNER) |
| |
| **BREWERY_OWNER** |
| * name (VARCHAR 150) |
| * desc* (TEXT) |
| * status* (boolean, czy właściciel zaakceptowany w danym języku czy |
| nie) |
| |
| **BEERING** |
| * who (FK, USER, null=True) |
| * whom (FK, USER) |
| * beer (FK, BEER) |
| * size (ENUM) |
| * referrer (VARCHAR 255, NULL=True) |
| * comment (VARCHAR 160, NULL=True) |
| * timestamp (datetime lub unix_timestamp) |
| * ip (VARCHAR) |
| |
| Uwagi: |
| Tabele USER, GROUP, PERMS, GALLERY i IMAGE wykorzystujemy jako wbudowane modele framework'a Django. |
| |
| MtM = many to many |
| |
| FK = Foreign Key |
| |
| *=pola wielojęzykowe |
| |
| |
==== Projektowanie powiązań (relacji) pomiędzy encjami ==== | ==== Projektowanie powiązań (relacji) pomiędzy encjami ==== |
| |
| {{:pl:dydaktyka:ztb:2010:projekty:thankswithbeer:thankswithbeer_erd.png}} |
| |
| ==== Kod SQL (PostgreSQL) bazy danych ==== |
| |
| <code sql> |
| BEGIN; |
| CREATE TABLE "beers_breweryownercontent" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "model_id" integer NOT NULL, |
| "language_id" integer NOT NULL REFERENCES "multilanguage_language" ("id") DEFERRABLE INITIALLY DEFERRED, |
| "desc" text NOT NULL, |
| "status" boolean NOT NULL |
| ) |
| ; |
| CREATE TABLE "beers_breweryowner" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "name" varchar(150) NOT NULL, |
| "brewery_owner_id" integer |
| ) |
| ; |
| ALTER TABLE "beers_breweryownercontent" ADD CONSTRAINT "model_id_refs_id_598fc7fb" FOREIGN KEY ("model_id") REFERENCES "beers_breweryowner" ("id") DEFERRABLE INITIALLY DEFERRED; |
| ALTER TABLE "beers_breweryowner" ADD CONSTRAINT "brewery_owner_id_refs_id_3d4e3b7b" FOREIGN KEY ("brewery_owner_id") REFERENCES "beers_breweryowner" ("id") DEFERRABLE INITIALLY DEFERRED; |
| CREATE TABLE "beers_brewerycontent" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "model_id" integer NOT NULL, |
| "language_id" integer NOT NULL REFERENCES "multilanguage_language" ("id") DEFERRABLE INITIALLY DEFERRED, |
| "desc" text NOT NULL, |
| "status" boolean NOT NULL |
| ) |
| ; |
| CREATE TABLE "beers_brewery" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "name" varchar(150) NOT NULL, |
| "brewery_owner_id" integer REFERENCES "beers_breweryowner" ("id") DEFERRABLE INITIALLY DEFERRED |
| ) |
| ; |
| ALTER TABLE "beers_brewerycontent" ADD CONSTRAINT "model_id_refs_id_2e86a681" FOREIGN KEY ("model_id") REFERENCES "beers_brewery" ("id") DEFERRABLE INITIALLY DEFERRED; |
| CREATE TABLE "beers_beercontent" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "model_id" integer NOT NULL, |
| "language_id" integer NOT NULL REFERENCES "multilanguage_language" ("id") DEFERRABLE INITIALLY DEFERRED, |
| "desc" text NOT NULL, |
| "status" boolean NOT NULL |
| ) |
| ; |
| CREATE TABLE "beers_beer" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "name" varchar(150) NOT NULL, |
| "brewery_id" integer NOT NULL REFERENCES "beers_brewery" ("id") DEFERRABLE INITIALLY DEFERRED, |
| "price_eur" double precision NOT NULL, |
| "lovers" integer NOT NULL, |
| "haters" integer NOT NULL, |
| "logo" varchar(100) NOT NULL, |
| "bottle" varchar(100), |
| "glass" varchar(100) |
| ) |
| ; |
| ALTER TABLE "beers_beercontent" ADD CONSTRAINT "model_id_refs_id_38ce73d1" FOREIGN KEY ("model_id") REFERENCES "beers_beer" ("id") DEFERRABLE INITIALLY DEFERRED; |
| CREATE TABLE "beers_hatedbeerratingcontent" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "model_id" integer NOT NULL, |
| "language_id" integer NOT NULL REFERENCES "multilanguage_language" ("id") DEFERRABLE INITIALLY DEFERRED, |
| "name" varchar(100) NOT NULL |
| ) |
| ; |
| CREATE TABLE "beers_hatedbeerrating" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "value" integer NOT NULL |
| ) |
| ; |
| ALTER TABLE "beers_hatedbeerratingcontent" ADD CONSTRAINT "model_id_refs_id_110e7a67" FOREIGN KEY ("model_id") REFERENCES "beers_hatedbeerrating" ("id") DEFERRABLE INITIALLY DEFERRED; |
| CREATE TABLE "beers_hatedbeer" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "user_id" integer NOT NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED, |
| "beer_id" integer NOT NULL REFERENCES "beers_beer" ("id") DEFERRABLE INITIALLY DEFERRED, |
| "rating_id" integer NOT NULL REFERENCES "beers_hatedbeerrating" ("id") DEFERRABLE INITIALLY DEFERRED |
| ) |
| ; |
| CREATE TABLE "beers_lovedbeerratingcontent" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "model_id" integer NOT NULL, |
| "language_id" integer NOT NULL REFERENCES "multilanguage_language" ("id") DEFERRABLE INITIALLY DEFERRED, |
| "name" varchar(100) NOT NULL |
| ) |
| ; |
| CREATE TABLE "beers_lovedbeerrating" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "value" integer NOT NULL |
| ) |
| ; |
| ALTER TABLE "beers_lovedbeerratingcontent" ADD CONSTRAINT "model_id_refs_id_4fbf0e87" FOREIGN KEY ("model_id") REFERENCES "beers_lovedbeerrating" ("id") DEFERRABLE INITIALLY DEFERRED; |
| CREATE TABLE "beers_lovedbeer" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "user_id" integer NOT NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED, |
| "beer_id" integer NOT NULL REFERENCES "beers_beer" ("id") DEFERRABLE INITIALLY DEFERRED, |
| "rating_id" integer NOT NULL REFERENCES "beers_lovedbeerrating" ("id") DEFERRABLE INITIALLY DEFERRED |
| ) |
| ; |
| CREATE TABLE "beers_beersize" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "size" double precision NOT NULL |
| ) |
| ; |
| CREATE TABLE "beers_beering" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "who_id" integer REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED, |
| "whom_id" integer NOT NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED, |
| "beer_id" integer NOT NULL REFERENCES "beers_beer" ("id") DEFERRABLE INITIALLY DEFERRED, |
| "beer_size_id" integer NOT NULL REFERENCES "beers_beersize" ("id") DEFERRABLE INITIALLY DEFERRED, |
| "referrer" varchar(255) NOT NULL, |
| "comment" varchar(160) NOT NULL, |
| "timestamp" timestamp with time zone NOT NULL, |
| "ip" varchar(15) NOT NULL |
| ) |
| ; |
| CREATE TABLE "auth_permission" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "name" varchar(50) NOT NULL, |
| "content_type_id" integer NOT NULL REFERENCES "django_content_type" ("id") DEFERRABLE INITIALLY DEFERRED, |
| "codename" varchar(100) NOT NULL, |
| UNIQUE ("content_type_id", "codename") |
| ) |
| ; |
| CREATE TABLE "auth_group" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "name" varchar(80) NOT NULL UNIQUE |
| ) |
| ; |
| CREATE TABLE "auth_user" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "username" varchar(30) NOT NULL UNIQUE, |
| "first_name" varchar(30) NOT NULL, |
| "last_name" varchar(30) NOT NULL, |
| "email" varchar(75) NOT NULL, |
| "password" varchar(128) NOT NULL, |
| "is_staff" boolean NOT NULL, |
| "is_active" boolean NOT NULL, |
| "is_superuser" boolean NOT NULL, |
| "last_login" timestamp with time zone NOT NULL, |
| "date_joined" timestamp with time zone NOT NULL |
| ) |
| ; |
| CREATE TABLE "auth_message" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "user_id" integer NOT NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED, |
| "message" text NOT NULL |
| ) |
| ; |
| CREATE TABLE "auth_group_permissions" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "group_id" integer NOT NULL REFERENCES "auth_group" ("id") DEFERRABLE INITIALLY DEFERRED, |
| "permission_id" integer NOT NULL REFERENCES "auth_permission" ("id") DEFERRABLE INITIALLY DEFERRED, |
| UNIQUE ("group_id", "permission_id") |
| ) |
| ; |
| CREATE TABLE "auth_user_groups" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "user_id" integer NOT NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED, |
| "group_id" integer NOT NULL REFERENCES "auth_group" ("id") DEFERRABLE INITIALLY DEFERRED, |
| UNIQUE ("user_id", "group_id") |
| ) |
| ; |
| CREATE TABLE "auth_user_user_permissions" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "user_id" integer NOT NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED, |
| "permission_id" integer NOT NULL REFERENCES "auth_permission" ("id") DEFERRABLE INITIALLY DEFERRED, |
| UNIQUE ("user_id", "permission_id") |
| ) |
| ; |
| CREATE TABLE "multilanguage_language" ( |
| "id" serial NOT NULL PRIMARY KEY, |
| "code" varchar(5) NOT NULL, |
| "name" varchar(16) NOT NULL |
| ) |
| ; |
| COMMIT; |
| </code> |
| |
| ==== Wygenerowany dokładny ERD z SQL ==== |
| |
| {{:pl:dydaktyka:ztb:2010:projekty:thankswithbeer:sql_erd.jpg|}} |
| |
==== Projekt diagramów STD ==== | ==== Projekt diagramów STD ==== |
| {{:pl:dydaktyka:ztb:2010:projekty:thankswithbeer:twb_std_uzytkownik.png|}}{{:pl:dydaktyka:ztb:2010:projekty:thankswithbeer:twb_std_gosc.png|}} |
| |
| ==== Wielojęzyczność ==== |
| |
| W projekcie zastosowaliśmy ulepszone rozwiązanie [[http://code.google.com/p/django-multilingual-model/|multilingualmodel]]. Ulepszenie polegało głównie na lepszej integracji z wbudowanym panelem administracyjnym Django. |
| |
| Całość działania systemu wielojęzykowego obrazuje przykładowy diagram ERD: |
| |
| {{:pl:dydaktyka:ztb:2010:projekty:thankswithbeer:language_erd.jpg|}} |
| |
| Na obrazku widzimy encję BEERS_BEER (unikalny wpis w bazie, który przechowuje główne informacje dla encji - informacje nie związane z językiem). Dla encji tej mamy powiązanie FK (model_id) w encji BEERS_BEERCONTENT, które zawiera pola do tłumaczenia - w przykładzie jedynie pole DESC i STATUS są tymi polami. |
| |
| Do całości brakuje jeszcze przyporządkowania języka dla danego wpisu w tabeli *CONTENT - zupełnie osobna encją MULTILANGUAGE_LANGUAGE. |
| |
| Takie podwójne tworzenie modeli (jeden podstawowy oraz drugi z powiązany z tłumaczeniami pól) stosujemy dla każdej encji wielojęzycznej. Dzięki takiemu rozwiązaniu możemy dodawać kolejne wersje językowe bez konieczności definiowania ich z góry - nic nie stoi na przeszkodzie aby dodać nowy język do tabeli MULTILANGUAGE_LANGUAGE, a następnie dodać wpis w BEERS_BEERCONTENT |
| |
| Aby zachować spójność danych można dodać jeszcze klucz UNIQUE(language, model_id) do każdego modelu *CONTENT, dzięki czemu na 100% nie będziemy mieli 2 wersji tłumaczeń tego samego języka. |