Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Начинающим";
Текущий архив: 2013.03.22;
Скачать: [xml.tar.bz2];

Вниз

TThread копирование файла в 2-х потоках   Найти похожие ветки 

 
PacMan ©   (2012-05-01 20:31) [0]

Помогите кто-нибудь.
Нужно в 2-х потоках копировать файл.
1-й поток читает файл и пишет данные в буфер1, затем содержимое буфера 1 передает в буфер 2.
2-й поток пишет из буфера 2 в файл.
бьюсь уже давно, ни как не могу понять как работают потоки.. как 1 заставить ждать, пока не выполнится что-либо во-2м...

листинг:
unit Unit1;

interface

uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, unit2, StdCtrls, SyncObjs, Gauges;

type
 TForm1 = class(TForm)
   Label1: TLabel;
   Label2: TLabel;
   Button1: TButton;
   Edit1: TEdit;
   Edit2: TEdit;
   OpenDialog1: TOpenDialog;
   SaveDialog1: TSaveDialog;
   Label3: TLabel;
   Gauge1: TGauge;
   procedure Button1Click(Sender: TObject);
   procedure Edit1Click(Sender: TObject);
   procedure Edit2Click(Sender: TObject);
 private
   { Private declarations }
 public
   { Public declarations }
 end;

var
 Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
 CriticalSection:=TCriticalSection.Create;
 stream1.Create(false);
 stream2.Create(false);
end;

procedure TForm1.Edit1Click(Sender: TObject);
begin
 if OpenDialog1.Execute then
   Edit1.Text := OpenDialog1.FileName;
end;

procedure TForm1.Edit2Click(Sender: TObject);
begin
 if SaveDialog1.Execute then
   Edit2.Text := SaveDialog1.FileName;
end;

end.

unit Unit2;

interface

uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, StdCtrls, SyncObjs;

type
 stream1 = class(TThread)

 protected

 public
   Procedure Podgot;
   Procedure ZabivBuf;
   Procedure Zakon;
   procedure Execute; override;
 end;

 stream2 = class(TThread)

 protected

 public
   procedure Execute; override;
   procedure FWrite;
 end;

 var
 f1, f2: File; // первый и второй файл
 buf1: array [1..8192] of Char; //буфер 1
 buf2: array [1..8192] of Char; //буфер 2
 sizefile, sizeread: Int64; //размер файла и размер прочитанного
 colRead, colWrite : Integer; //прочитано и записано
 fOtkuda, fKuda : String; //адреса и имена файлов
 CriticalSection: TCriticalSection;
 flag: Boolean;

 implementation
 uses unit1;

procedure stream1.Execute;
begin
 Podgot;
 while colRead = colWrite do
 begin
   if flag=false then   //если буфер 2 уже пустой
   begin
     CriticalSection.Enter;
     ZabivBuf;

   end;
 end;
 Zakon;
end;

procedure stream2.Execute;
begin
 while colRead = colWrite do
 begin
   if flag=True then   //если буфер 2 уже пустой
   begin
     CriticalSection.Enter;
     Fwrite;
   end;
 end;
end;

Procedure stream1.Podgot;       // подготовка
var i:integer;
begin
 {i-} //даем компилятору директиву, чтобы не отслеживал ошибки ввода-вывода:
 if (Form1.Edit1.Text="") or (Form1.Edit2.Text="") then   //проверяем, указаны ли файлы. если нет - выходим
 begin
   ShowMessage("Вы не указали данные для копирования");
   Exit;
 end;
 //Try
   AssignFile(f1, Form1.Edit1.Text);  //связываем файловые переменные:
   AssignFile(f2, Form1.Edit2.Text);
   Reset(F1, 1);  //связываем файловые переменные:
   sizefile := FileSize(f1); //определяем его размер в переменную:
   Form1.Label3.Caption := "Размер файла: "+IntToStr(Round(sizefile / 8192)) + " Кб.";     //отображаем размер файла в килобайтах:
   Rewrite(f2, 1);  //создаем или перезаписываем второй файл:
   colRead := 0;    //делаем, пока не достигнут конец исходного файла
   colWrite := 0;
   sizeread := 0;
   Screen.Cursor := crHourGlass; //песочные часы
   {i+}
end;

Procedure Stream1.ZabivBuf;
var
   i:integer;    //while colRead = colWrite do
begin
 {i-}
 BlockRead(f1, Buf1, SizeOf(Buf1), colRead);
 //if colRead = 0 then break;

   for I := 1 to 8192 do
     Buf2[i]:=Buf1[i];  //Буфер1 в Буфер2

 Form1.Gauge1.Progress := Round(100*sizeread/sizefile);
 Flag:=True;
 CriticalSection.Leave;
 {i+}
end;

Procedure Stream1.Zakon;
begin
 {i-}
   Screen.Cursor := crDefault; //обычный вид курсора
 //Finally
   CloseFile(f1);
   CloseFile(f2);
 //end; //try
 fOtkuda := Form1.Edit1.Text;  //исправляем дату
 fKuda := Form1.Edit2.Text;
 if IOResult <> 0 then
   Application.MessageBox("Ошибка при копировании файла!", "Внимание!!!",
     MB_OK+MB_ICONERROR)
 else ShowMessage("Копирование  завершено успешно");
 //включаем обработчик компилятором ошибок
 {i+}
End;

procedure stream2.FWrite;
var
   i: Integer;
begin
   BlockWrite(f2, Buf2, colRead, colWrite);
   sizeread := sizeread + colRead;
   Flag:=False;
   CriticalSection.Leave;

end;

end.


 
sniknik ©   (2012-05-01 20:44) [1]

> как 1 заставить ждать, пока не выполнится что-либо во-2м...
а то что по сути это станет одним потоком, просто с разнесенным кодом/логикой на два тебя не смущает?

получается типа, "хочу нести воду в 2х ведрах, но так чтобы пол пути в одном, пол в другом. как на полпути воду в ведрах местами поменять? емкостей то больше нет".


 
PacMan ©   (2012-05-01 20:52) [2]

меня это не смущает, т.к. по заданию мне нужно копировать файл в 2-х потоках, и препод ничего не может мне подсказать по этому поводу.
Если у кого-то есть "знание" как сделать хотя бы это, буду благодарен.
Если кто-то может показать как сделать оптимальнее, также буду благодарен.


 
sniknik ©   (2012-05-01 21:27) [3]

> как 1 заставить ждать, пока не выполнится что-либо во-2м...
уже используется - CriticalSection.Enter; во втором заставит первый ждать, не скажу насчет правильности, логики его использования у тебя, но это вот оно.

и кстати у тебя куча всего чего нельзя в потоках... типа прямого обращения к vcl.


 
PacMan ©   (2012-05-01 21:36) [4]

ну синхронайз пока не трогаю...  программа запускается и так.

при запуске я выбираю исходный файл, выбираю куда и под каким именем его скопировать...
по кнопке "что-то происходит" и в конце выводится "копирование успешно завершено".
открываю директорию назначения и вижу файлик с нужным именем... !НО! его размер 0 байт.
смотрел пошаговое выполнение, у меня запускается 1-й поток, проходит круг... ииииии..... "копирование завершено".

чтобы было понятнее что и как, могу на почту скинуть прогу.


 
QAZ   (2012-05-01 22:16) [5]


> sniknik ©   (01.05.12 20:44) [1]

там на самом деле гинеальная идея заложена :)
типа пока один поток пишет первый кусок,другой читает второй и тд
но она имеет смысл только при наличии 2х жестких дисков , если источник на одном а приемник на другом


 
Сергей М. ©   (2012-05-01 22:18) [6]


> 1-й поток читает файл и пишет данные в буфер1, затем содержимое
> буфера 1 передает в буфер 2.
> 2-й поток пишет из буфера 2 в файл.


Это ты сам придумал или препод навязал тебе именно такую логику ?


 
sniknik ©   (2012-05-01 22:50) [7]

оптимальностью не пахнет, сама логика кривая, но... более... не знаю даже как сказать, но работоспособно это точно. -
unit Unit1;

interface

uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, Gauges, StdCtrls;

const
 WM_WRITE = WM_USER + 101;

type
 TForm1 = class(TForm)
   Edit1: TEdit;
   Edit2: TEdit;
   Button1: TButton;
   Gauge1: TGauge;
   procedure Button1Click(Sender: TObject);
 private
   procedure WMWrite(var Msg: TMessage); message WM_WRITE;
 public
 end;

var
 Form1: TForm1;

implementation

{$R *.dfm}

const
 BufSize  = 4096;
 WM_BUF   = WM_USER + 102;

type
 TFileRead = class(TThread)
 protected
   F: File;
   FName: string;
   ThreadID2: integer;
   procedure Execute; override;
 public
   constructor Create(ID2: integer; const Name: string);
 end;

 TFileWrite = class(TThread)
 protected
   F: File;
   FName: string;
   MainHandle: THandle;
   procedure Execute; override;
 public
   constructor Create(Handle: THandle; const Name: string);
 end;

constructor TFileRead.Create(ID2: integer; const Name: string);
begin
 ThreadID2:= ID2;
 FName    := Name;
 inherited Create(false);
end;

procedure TFileRead.Execute;
var
 Buf: PChar;
 NumRead: integer;
begin
 AssignFile(F, FName);
 Reset(F, 1);

 repeat
   GetMem(Buf, BufSize);
   BlockRead(F, Buf^, BufSize, NumRead);
   PostThreadMessage(ThreadID2, WM_BUF, integer(Buf), NumRead);
 until (NumRead = 0);

 CloseFile(F);
end;

constructor TFileWrite.Create(Handle: THandle; const Name: string);
begin
 MainHandle:= Handle;
 FName     := Name;
 inherited Create(false);
end;

procedure TFileWrite.Execute;
var
 Buf: PChar;
 NumWritten, Count: integer;
 Msg: TMsg;
begin
 AssignFile(F, FName);
 Rewrite(F, 1);

 Count:= 0;
 while GetMessage(Msg, 0, 0, 0) do begin
   DispatchMessage(Msg);

   Buf  := Pointer(Msg.wParam);
   Count:= Count + Msg.lParam;
   BlockWrite(F, Buf^, Msg.lParam, NumWritten);
   FreeMem(Buf);

   PostMessage(MainHandle, WM_WRITE, Count, 0);
 end;

 CloseFile(F);
end;

procedure TForm1.WMWrite(var Msg: TMessage);
begin
 Gauge1.Progress:= Msg.WParam;
 Button1.Enabled:= Gauge1.Progress = Gauge1.MaxValue;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
 FileWrite: TFileWrite;
begin
 with TFileStream.Create(Edit1.Text, fmOpenRead) do
 try
   Gauge1.MaxValue:= Size;
 finally
   Free;
 end;
 Button1.Enabled:= false;

 FileWrite:= TFileWrite.Create(Handle, Edit2.Text);
 TFileRead.Create(FileWrite.ThreadID, Edit1.Text);
end;

end.


 
sniknik ©   (2012-05-01 23:05) [8]

кстати в цикле второго потока (или вообще) ошибка... он не завершается. исправь.


 
Sha ©   (2012-05-02 02:08) [9]

http://www.delphimaster.net/view/2-1202729073


 
PacMan ©   (2012-05-02 10:24) [10]


> Это ты сам придумал или препод навязал тебе именно такую
> логику ?


на самом деле задание звучит так:
"Разработать компонент, реализующий копирование файлов, при помощи тройной буферизации. Программа должна использовать потоки. Первый поток считывает блок данных из указанного файла. Второй поток записывает этот блок данных в файл. Буферизацию реализовать из 2-х массивов, состоящих из 3-х элементов. Каждый элемент - это буфер. Первый массив отвечает за чтение из файла, а второй за запись. Первый поток получает имя файла, берет пустой элемент из первого массива, записывает в него блок данных и передает второму массиву этот блок данных. При этом буфер, в котором считали блок данных, обнуляется. Второй поток, видя, что во втором массиве есть полный буфер, записывает этот буфер в файл и обнуляет его. При этом, в первом массиве выделяется новый буфер. И так пока весь файл не будет скопирован"

!НО! я даже малейшего представления не имею как это реализовать. Вот и решил упростить "слегка".

если у кого-нибудь есть идеи как это сделать, или еще лучше исходники... буду просто счастлив))
надеюсь не сильно запутал.


 
PacMan ©   (2012-05-02 10:30) [11]

Есть коммерческое предложение, т.к. сам это все равно не напишу.
Если кто желает заморочиться по заданию, договоримся о цене.
Кто надумает, пишите в мыло yrungxay@mail.ru


 
Sha ©   (2012-05-02 11:36) [12]

> PacMan ©   (02.05.12 10:24) [10]

Формулировка ужасная. Это точно оригинальный текст "от препода"?


 
PacMan ©   (2012-05-02 11:52) [13]

ага)
это сфотографировано лично)


 
Cobalt ©   (2012-05-02 11:54) [14]

Имитация обработки - один поток откуда-то получает данные, складирует их в буфер.
Второй поток - мониторит поступлени данных, и записывает их в файл, например, в базу данных.
Вполне жизненная ситуация.


 
Sha ©   (2012-05-02 12:57) [15]

> PacMan ©   (02.05.12 10:24) [10]
> при помощи тройной буферизации.

обычно говорят буферизации чего, какой операции

> Программа должна использовать потоки

ну раз надо, значит, надо

> Буферизацию реализовать из 2-х массивов, состоящих из 3-х элементов.
всего 3 или 3 в каждом массиве? элементы - это что? обычно указатели

> Первый массив отвечает за чтение из файла, а второй за запись.

переменная ни за что никому не отвечает

> Первый поток получает имя файла,
> берет пустой элемент из первого массива,
> записывает в него блок данных..

значит, элемент - не указатель?

> и передает второму массиву этот блок данных.

передать переменной? она же памятник
копирование данных или указателей?
кто об этом и как узнает?
а если некуда передавать? табличка "занято" 3 раза?

> При этом буфер, в котором считали блок данных, обнуляется.

нафига? если в нем были данные - то это бред
может, указатель, хотя это тоже не оптимал

> Второй поток, видя, что во втором массиве есть полный буфер,

просто песня! КАК он это видит?

> записывает этот буфер в файл и обнуляет его.

опять же нафига?
опять же, если видит, но не может? типа еще е все доделал

> При этом, в первом массиве выделяется новый буфер.

вроде мы не удаляли ничего,
в таком случае лимит в 3 буфера будет превышен!

> Cobalt

Конечно, можно мониторить поступление телефонного звонка,
но, вероятно, намного проще брать трубку по звонку.


 
Anatoly Podgoretsky ©   (2012-05-02 13:43) [16]

> Sha  (02.05.2012 12:57:15)  [15]

Это тест на испытание копипастеров, не выживет так не выживет.


 
PacMan ©   (2012-05-02 19:41) [17]


> Sha ©   (02.05.12 12:57) [15]


Это текст задания слово в слово.
Причем сама препод не может ни чего пояснить по нему.
Спрашиваю из элементов какого типа состоят массивы(массив байт или строки или еще что)? Отвечает, что как мне удобнее пусть так и будет, а про копирование вообще ни чего сказать не может.
как-то так.


 
Сергей М. ©   (2012-05-02 20:44) [18]


> сама


Это многое что объясняет)


 
Андреевич   (2012-05-02 21:13) [19]


> там на самом деле гинеальная идея заложена :)
> типа пока один поток пишет первый кусок,другой читает второй
> и тд
> но она имеет смысл только при наличии 2х жестких дисков
> , если источник на одном а приемник на другом

или если источники типа \\server\share\file.txt


 
Anatoly Podgoretsky ©   (2012-05-02 21:36) [20]

> PacMan  (02.05.2012 19:41:17)  [17]

А что ты ей плохого сделал?


 
Сергей М. ©   (2012-05-02 22:43) [21]


> Anatoly Podgoretsky ©   (02.05.12 21:36) [20]


А может просто не сделал, притом что должен был сделать ?
imho, сей вариант наиболее вероятен)


 
Sha ©   (2012-05-03 01:20) [22]

> PacMan ©

Почитай про кольцевой буфер клавиатуры,
прерывания 9 и 16, можно поверхностно.
Изобрази ей нечто похожее.


 
Германн ©   (2012-05-03 01:54) [23]


> Sha ©   (03.05.12 01:20) [22]

Ностальгия?


 
han_malign   (2012-05-03 09:07) [24]


> Почитай про кольцевой буфер клавиатуры

- низзя, сказано "тройная буферизация", значит так и надо делать - нормальный FIFO можно сделать в качестве факультатива...

По сути(технический перевод):
1. есть пул блоков чтения и пул блоков записи фиксированной максимальной глубины...
2. пул чтения изначально заполнен указателями на блоки буфера(скажем размера 64K)
3. поток чтения выбирает блок из пула чтения, заполняет его и помещает в пул записи.
4. поток записи выбирает блок из пула записи, обрабатывает - и помещает в пул чтения.
5. соответственно, если в пуле нет блоков, а "процесс" не завершен - поток приостанавливается.

Тупой спин, кстати - тоже не возбраняется - в некоторых случаях(когда процессоров/ядер не меньше чем потоков и нагрузка на потоки сбалансирована) он увеличивает производительность за счет меньшего количества переключения контекста потоков...

А по делу - это делается двумя счетчиками прочитанных/записанных данных - раздельный доступ к частям буфера обеспечивается счетчиками:
cbR += read(pb[cbR mod sizeof(pb)], min(MAX_BLOCK,sizeof(pb) - (cbR-cbW)));
cbW += write(pb[cbW mod sizeof(pb)], min(MAX_BLOCK,cbR-cbW));


З.Ы. Кстати - в этом варианте уже посчитан последний блок неполного размера...

З.З.Ы. И никакой тройной буферизации нет, буфер в рамках одного уровня одного тракта - один, хотя и может состоять из произвольного числа блоков(т.н. "FIFO-половинки")...


 
PacMan ©   (2012-05-03 09:37) [25]


> Anatoly Podgoretsky ©   (02.05.12 21:36) [20]

хотел другое задание взять... сказала: "Нет, оно не интересное, я тебе вот это дам..."
и вот что заимел(


 
Медвежонок Пятачок ©   (2012-05-03 09:44) [26]

она на тебя просто запала. запишись на дополнительное индивидуальное занятие. вечернее.


 
sniknik ©   (2012-05-03 09:45) [27]

> и вот что заимел(
заслуженная двойка по предмету... даже видя перед собой решение, не понимаешь, что это такое.


 
PacMan ©   (2012-05-03 11:13) [28]

))) Решение вижу, понимаю алгоритм... не понимаю как реализовать.
В  моем примере 1-й поток начинает работать, но проходит только 1 круг, из-за того что 2-й поток не хочет работать.


 
sniknik ©   (2012-05-03 12:01) [29]

> ))) Решение вижу, понимаю алгоритм... не понимаю как реализовать.
угадал все буквы, не смог назвать слово... это нельзя назвать "понимаю".

> В  моем примере 1-й поток начинает работать, но проходит только 1 круг, из-за того что 2-й поток не хочет работать.
твой пример, это набор функций почти каждая из которых "не в том месте и не в то время".

вот что ты сделал по поводу
> и кстати у тебя куча всего чего нельзя в потоках... типа прямого обращения к vcl.
?
а ведь это может приводить к блокировкам, "локированию" потока. ... не думаю что ты "нарвался" именно на это (более вероятно кривая логика), но рассматривать код в котором такие очевидные глюки вряд ли кто будет (я точно нет).


 
Медвежонок Пятачок ©   (2012-05-03 12:21) [30]

стартуем два потока.
первый распределяет буфер и читает файл.
заполнив буфер, первый поток посылает средмессадж второму и сообщает адрес и длину буфера.
второй поток вынимает данные буфера в файл, затем грохает буфер и усыпает.
первый поток, закончив с одним буфером, распределяет второй и снова оповещает второй поток о готовой порции данных.
второй поток просыпается и повторяет операцию.
после того, как файл прочитан целиком, первый поток оповещает второй, что можно закругляться и идти пить пиво.


 
Sha ©   (2012-05-03 12:23) [31]

> han_malign   (03.05.12 09:07) [24]

В данном случае пул и есть кольцевой буфер,
т.к. мы имеем дело с последовательными операциями чтения и записи.

Если уж делать по теории,
то поток чтения занимается только чтением и ничего не знает о потоке записи,
а поток записи занимается только записью и ничего не знает о потоке чтения.


 
sniknik ©   (2012-05-03 12:48) [32]

> Если уж делать по теории,
> то поток чтения занимается только чтением и ничего не знает о потоке записи,
> а поток записи занимается только записью и ничего не знает о потоке чтения.
посмотри код в [7], так и сделано.
условие завершения второго потока только забыл, 1 строка, после решил оставить это автору топика.


 
Sha ©   (2012-05-03 14:01) [33]

> sniknik ©   (03.05.12 12:48) [32]

сразу бросилось в глаза,
что код [7] легко исчерпает память при быстром чтении и медленной записи


 
han_malign   (2012-05-03 14:10) [34]


> что код [7] легко исчерпает память при быстром чтении и медленной записи

- и будет утечка, если PostThreadMessage произойдет раньше оппозитного GetMessage...


 
Медвежонок Пятачок ©   (2012-05-03 14:19) [35]

что код [7] легко исчерпает память при быстром чтении и медленной записи

какая разница, если даже это реально произойдет?
это же абстрактное дурацкое учебное задание ради самого задания.


 
Anatoly Podgoretsky ©   (2012-05-03 14:23) [36]

А может от него надо избавиться, тогда задание предстает совсем в другом свете.


 
sniknik ©   (2012-05-03 14:26) [37]

> что код [7] легко исчерпает память при быстром чтении и медленной записи
естественно. но цель примера не в "копипасте", как понял задача учебная.
а сделать счетчик/ограничитель на например 5 буферов, не проблема...

> - и будет утечка, если PostThreadMessage произойдет раньше оппозитного GetMessage...
с чего это оно произойдет раньше? первый поток только создается когда у второго уже ThreadID есть, т.е. уже "крутится".


 
sniknik ©   (2012-05-03 14:28) [38]

> это же абстрактное дурацкое учебное задание ради самого задания.
+1
на понимание. которого автор якобы достиг ([28]) но почему то все одно "не получается".


 
sniknik ©   (2012-05-03 14:35) [39]

>> - и будет утечка, если PostThreadMessage произойдет раньше оппозитного GetMessage...
> с чего это оно произойдет раньше? первый поток только создается когда у второго уже ThreadID есть, т.е. уже "крутится".
или имеется ввиду промежуток между стартом, и началом ожидания, там где пара команд открытия файла (если задержка), ну и что там дельфя своего добавляет?
в этом случае событие будет ждать в очереди, пока его явно из нее не выкинут. т.е. однозначно дождется цикла.


 
Вариант   (2012-05-03 15:03) [40]


> sniknik ©   (03.05.12 14:35) [39]


>  с чего это оно произойдет раньше? первый поток только создается
> когда у второго уже ThreadID есть, т.е. уже "крутится"

Может начать крутиться и быть остановленным системой, не дойдя до первого вызова GetMessage. В этом случае еще не будет очереди сообщений потока, тогда PostThreadNessage вернет fails. Вероятность этого не нулевая, хотя и повторяемость события не очень большая (если не использовать например отладчик для эмуляции), сам натыкался на такое, хотя первой командой в Execute потока был PeekMessage, просто поток был остановлен планировщиком. Поэтому было бы неплохо после PeekMessage  сигналить в запускающий поток, что мой поток готов принимать сообщения и только потом уже разрешать слать ему сообщения.....  ну  или вариант по MSDN сценарию...



Страницы: 1 2 вся ветка

Форум: "Начинающим";
Текущий архив: 2013.03.22;
Скачать: [xml.tar.bz2];

Наверх




Память: 0.59 MB
Время: 0.073 c
15-1333280133
sniknik
2012-04-01 15:35
2013.03.22
Сделать открытие popup в IE как в mozilla


6-1262861262
__Алексей__
2010-01-07 13:47
2013.03.22
Глюки ARP-кеша и получение MAC существующих хостов


15-1347959351
DevilDevil
2012-09-18 13:09
2013.03.22
Методы компиляции и оптимизации машинных команд


15-1332165101
Abra
2012-03-19 17:51
2013.03.22
что-то с логикой не понятно,


1-1300272526
Unknown_user
2011-03-16 13:48
2013.03.22
Ограничение скроллинга в 32767





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский