Zapraszam do kontaktu w wygodnej dla Ciebie formie, w celu ustalenia szczegółów.

Na zapytanie odpowiem w dni robocze, do 12 godzin.

Jeśli wolisz, możesz też zadzwonić: tel. 503 692 370

Witryna poświęcona programowaniu 

RSS
Home Kuty Language Changer

Kuty Language Changer

Chciałem Wam zaprezentować kolejny program przeznaczony dla „developerów” chcących rozbudować swoją aplikację o nową funkcjonalność. Gdy wydajemy programy, które chcemy aby były rozpowszechniane na całym świecie przydało by się umożliwić zmianę języka na inny niż polski czy angielski. Na przeciw wychodzę z Kuty Language Chenger-em. Program wraz z kodem zamieszczonym w Waszych programach daje możliwość tłumaczenia go na różne języki.

Zasada jest prosta, w Waszym programie wywołujecie kilka napisanych przeze mnie procedur, a następnie ładujecie wszystko do Kuty Language Changer, tłumaczycie i znowu wywołujecie moje procedury, które powodują zmianę języka. A więc zaczynamy. Tym razem zacznę od prezentacji kodu, a następnie przedstawię obsługę programu.

Na początku prezentuję Wam mój unit PropertiesINI.pas. Zawarte są w nim wszystkie funkcje potrzebne do odczytu i zapisu języka. Najpierw deklarujemy typy, zmienne i nazwy procedur.

type
  TActionKind = (akAccept, akLeave);

const
  OdstepZagniezdzenia = '.';

function IsNullString(Pole: Variant; ZamiastNull: String): String;

function  HasProp(MyComponent: TComponent; Prop: String): Boolean;
procedure SetProp(MyComponent: TComponent; Prop: String; Value: string);

function ReturnComponentPropertiesINI(Component: TObject; PriorClass: string;
                                        TypeKinds: OleVariant; ActionTypeKinds: TActionKind;
                                        Properties: OleVariant;  ActionProperties: TActionKind):string;

function ReturnComponentPropertiesINIForForm(Sender: TForm;
                                        TypeKinds: OleVariant;  ActionTypeKinds: TActionKind;
                                        Properties: OleVariant; ActionProperties: TActionKind): WideString;

function SetComponentPropertiesINI(Sender: TForm; Linia: string;
                                       Properties: OleVariant; ActionProperties: TActionKind ): Boolean;

function SetComponentPropertiesINIForForm(Ini_File: string; Sender: TForm;
                                       Properties: OleVariant; ActionProperties: TActionKind): String;

Funkcja poniżej zamienia nam wartość Null na określony ciąg znaków string.

function IsNullString(Pole: Variant; ZamiastNull: String): String;
begin
  Result := ZamiastNull;
  if VarIsNull(Pole) then
    Result := ZamiastNull
  else
    Result := VarToStr(Pole);
end;

Funkcja sprawdzająca czy dany komponent posiada określone property znajduje się poniżej.

Function HasProp(MyComponent: TComponent; Prop: String): Boolean;
var
  PropInfo: PPropInfo;
begin
  PropInfo := GetPropInfo(MyComponent.ClassInfo, Prop);
  Result := (PropInfo <> NIL);
end;

SetProp – bardzo ważna procedura dzięki której będziemy ustawiać dane tłumaczenie do naszych kontrolek. Korzysta ona z Delphi RTTI (Run Time Type Information) .

procedure SetProp(MyComponent: TComponent; Prop: String; Value: string);
var
  PropInfo: PPropInfo;
//  Bmp: TBitmap;
  LStrs: TStrings;
begin
  try
    PropInfo := GetPropInfo(MyComponent.ClassInfo, Prop);
    if PropInfo <> NIL then
    begin
      if PropInfo.PropType^.Kind <> tkClass then
      begin
        SetPropValue(MyComponent, Prop, Value);
      end else
      begin
        if PropInfo.PropType^^.Name = 'TStrings' then
        begin
          LStrs := TStringList.Create;
          try
            ExtractStrings([';'],[' '],PAnsiChar(Value),LStrs);
            SetObjectProp(MyComponent, Prop, LStrs);
          finally
            FreeAndNil(LStrs);
          end;
        end;
      end;
    end;
  except

  end;
end;

Funkcja zwracająca właściwości danego komponentu z pominięciem właściwości metod.
Component – komponent dla którego właściwości chcemy otrzymać
PriorClass – jest to nazwa tego komponentu
TypeKinds – typy property
ActionTypeKinds – akAccept/akLeave – akceptuj/pomiń typy podane w TypeKinds
Properties – nazwy property
ActionProperties – akAccept/akLeave – akceptuj/pomiń te property podane w Properties

function ReturnComponentPropertiesINI(Component: TObject; PriorClass: string;
                                        TypeKinds: OleVariant;  ActionTypeKinds: TActionKind;
                                        Properties: OleVariant; ActionProperties: TActionKind ):string;
var
  ObjectData: PTypeInfo;
  ObjectInfo: PTypeData;
  PropList: PPropList;
  i, j, k: Integer;
//  LStrList: TStringList;
  Obiekt: TObject;

  Linia: string;
  AkceptujTypeKind, AkceptujWlasciwosc: Boolean;
begin
  Result :='';
  ObjectData := Component.ClassInfo;
  ObjectInfo := GetTypeData(ObjectData);
  GetMem(PropList, SizeOf(PPropInfo) * ObjectInfo.PropCount);
  try
    GetPropInfos(Component.ClassInfo, PropList);
    for i := 0 to ObjectInfo.PropCount - 1 do
      if not (PropList[i]^.PropType^.Kind = tkMethod) then // odrzucenie wszystkich wlasciwosci zdarzeniowych
      begin
        if (PropList[i]^.PropType^.Kind <> tkClass) then
        begin
          if VarIsNull(Properties) then
            AkceptujWlasciwosc:= True
          else
          begin
            AkceptujWlasciwosc:= not (ActionProperties = akAccept);
            for j:=0 to VarArrayHighBound(Properties, 1) do
            begin
              if UpperCase(PropList[i]^.Name) = UpperCase(Properties[j]) then
              begin
                AkceptujWlasciwosc:= (ActionProperties = akAccept);
                Break;
              end;
            end;
          end;

          if AkceptujWlasciwosc then
          begin
            if VarIsNull(TypeKinds) then
              AkceptujTypeKind:= True
            else
            begin
              AkceptujTypeKind:= not (ActionTypeKinds = akAccept);
              for k:=0 to VarArrayHighBound(TypeKinds, 1) do
              begin
                if PropList[i]^.PropType^.Kind = TypeKinds[k] then
                begin
                  AkceptujTypeKind:= (ActionTypeKinds = akAccept);
                  Break;
                end;
              end;
            end;

            if AkceptujTypeKind then
            begin
              Linia := PriorClass+OdstepZagniezdzenia+PropList[i]^.Name + '='
                            + VarToStr(GetPropValue(Component,PropList[i]^.Name));
              if Linia <> '' then
                Result := Result + IfThen(Result <> '', sLineBreak, '') + Linia;

            end;
          end;
        end else
        begin
          Obiekt := GetObjectProp(Component,PropList[i]^.Name);
          if Obiekt <> nil then
          begin
            Linia := ReturnComponentPropertiesINI(Obiekt,
                      PriorClass+OdstepZagniezdzenia + PropList[i]^.Name,
                      TypeKinds, ActionTypeKinds, Properties, ActionProperties);
            if Linia <> '' then
              Result := Result + IfThen(Result <> '', sLineBreak, '') + Linia;
          end;
        end;
      end;
  finally
    FreeMem(PropList, SizeOf(PPropInfo) * ObjectInfo.PropCount);
  end;
end;

ReturnComponentPropertiesINIForForm jest funkcją korzystającą z funkcji zamieszczonej wyżej. Zwraca ona właściwości dla całej formy.

function ReturnComponentPropertiesINIForForm(Sender: TForm;
                                        TypeKinds: OleVariant;  ActionTypeKinds: TActionKind;
                                        Properties: OleVariant; ActionProperties: TActionKind): WideString;
var
  i: Integer;
  Linia: string;
  LStrLista: TStringList;
begin
  Result := '';
  LStrLista := TStringList.Create;
  try
    LStrLista.Add('['+Sender.Name+']');
    Linia := ReturnComponentPropertiesINI(Sender,
              Sender.Name,
              TypeKinds , ActionTypeKinds,
              Properties, ActionProperties);
    if Linia <> '' then
       LStrLista.Text:= LStrLista.Text + Linia;

    for i:=0 to TComponent(Sender).ComponentCount - 1 do
    begin
      Linia := ReturnComponentPropertiesINI(Sender.Components[i],
                Sender.Components[i].Name,
                TypeKinds, ActionTypeKinds,
                Properties, ActionProperties);
      if Linia <> '' then
         LStrLista.Text := LStrLista.Text + Linia;
    end;
    LStrLista.Sort;

    Result := LStrLista.Text;
  finally
    FreeAndNil(LStrLista);
  end;
end;

Procedury poniższe służą do przypisywania wartości właściwością. Pierwsza SetComponentPropertiesINI ustawia właściwość według linii podanej w parametrze wejściowym. Parametr Linia może przyjmować taką wartość: Button1.caption=to jest caption button1. Resztę parametrów wykorzystujemy w ten sam sposób.

function SetComponentPropertiesINI(Sender: TForm; Linia: string;
            Properties: OleVariant; ActionProperties: TActionKind ): Boolean;
var
  LComponentProperty, LValue: string;
  LPosRownaSie: Integer;
  LLength:Integer;

  AkceptujWlasciwosc: Boolean;

  i, j:Integer;
  LStrListProperty: TStringList;
  LPropertyName: string;
  LObiekt: TObject;

begin
  Result := False;
  LStrListProperty := TStringList.Create;
  try
    LPosRownaSie        := Pos('=', Linia);
    LLength             := Length(Linia);
    LComponentProperty  := Copy(Linia, 1,LPosRownaSie-1);
    LValue              := Copy(Linia, LPosRownaSie+1, LLength - LPosRownaSie);

    ExtractStrings([OdstepZagniezdzenia], [' '], PAnsiChar(LComponentProperty), LStrListProperty);
    if LStrListProperty.Count       Exit;

    LObiekt := Sender.FindComponent(LStrListProperty[0]);
    if LObiekt = nil then
      Exit;

    for i:=1 to LStrListProperty.Count - 1 do
    begin
      LPropertyName := LStrListProperty[i];
      if i < LStrListProperty.Count - 1 then
      begin
        LObiekt := GetObjectProp(LObiekt, LPropertyName);
      end else
      begin
        if VarIsNull(Properties) then
          AkceptujWlasciwosc:= True
        else
        begin
          AkceptujWlasciwosc:= not (ActionProperties = akAccept);
          for j:=0 to VarArrayHighBound(Properties, 1) do
          begin
            if UpperCase(LPropertyName) = UpperCase(Properties[j]) then
            begin
              AkceptujWlasciwosc:= (ActionProperties = akAccept);
              Break;
            end;
          end;
        end;

        if AkceptujWlasciwosc then
        begin
          SetProp(TComponent(LObiekt), LPropertyName, IsNullString(LValue,''));
        end;
      end;
    end;
    Result := True;
  finally
    FreeAndNil(LStrListProperty);
  end;
end;

Funkcja ustawia właściwości odczytane z pliku o ścieżce „Ini_File” dla formatki „Sender” z akceptacją/pominięciem właściwości podanych w „Properties”.

function SetComponentPropertiesINIForForm(Ini_File: string; Sender: TForm;
                                       Properties: OleVariant; ActionProperties: TActionKind ): string;
var
  LIni: TIniFile;
  LStrList: TStringList;
  i: Integer;
begin
  Result := '';
  if FileExists(Ini_File) then
  begin
    LIni := TIniFile.Create(Ini_File);
    try
      LStrList := TStringList.Create;
      try
        LIni.ReadSectionValues(Sender.Name, LStrList);
        Result := LStrList.Text;
        for i:=0 to LStrList.Count - 1 do
        begin
          SetComponentPropertiesINI(Sender, LStrList[i], Properties, ActionProperties);
        end;
      finally
        FreeAndNil(LStrList);
      end;
    finally
      FreeAndNil(LIni);
    end;
  end else
    ShowMessage('Plik: "'+Ini_File+'" nie istnieje.');
end;

Tu kończy się ten unit. Za pomocą niego napiszemy procedury do wczytywania i zapisywania języka naszej aplikacji.

Procedura SaveLang powoduje zapisanie wszystkich komponentów z wartościami właściwości. Znajduje się tam pewien fragment kodu:

    LmStringContainer:='';
    for i:=0 to mStringContainer.Lines.Count - 1 do
    begin
      if i = 0 then
        LmStringContainer := LmStringContainer + mStringContainer.Lines[i]
      else
        LmStringContainer := LmStringContainer + ';' + mStringContainer.Lines[i]

    end;
    LStrList.Add('mStringContainerTranslated.Lines='+LmStringContainer);

Jest to pewne rozwiązanie problemu z tekstem zawartym w kodzie. Jeżeli chcemy w kodzie programu zamieścić np.

if cos tam then
begin
  showmessage('Jakiś tekst po polsku');
end;

Kładziemy na formatkę dwa komponenty TMemo, nazywamy je mStringContainer, mStringContainerTranslated i ustawiamy im Visible:=false. Każdy tekst który chcemy wstawić w kodzie dodajemy do nowej linii komponentu mStringContainer, a kod zmieniamy na:

if cos tam then
begin
  showmessage(Translate('Jakiś tekst po polsku'));
end;

Funkcja Translate zostanie wytłumaczona troszkę niżej.

Procedura SaveLang zapisuje wszystkie kontolki przez nas oznaczone do pliku. Plik ten będziemy później wykorzystywać do tłumaczenia w Kuty Language Changer.

procedure SaveLang(FilePath: TFileName = 'Polski.Klang');
var
  LStrList: TStringList;
  i: Integer;
  LmStringContainer: string;
begin
  if FilePath = '' then Exit;
  LstrList := TStringList.Create;
  try
    LStrList.Text := ReturnComponentPropertiesINIForForm(self,
                VarArrayOf([tkChar, tkString, tkWChar, tkLString, tkWString]) , akAccept,
                VarArrayOf(['Caption', 'Hint', 'Title', 'Text']), akAccept);
    LmStringContainer:='';
    for i:=0 to mStringContainer.Lines.Count - 1 do
    begin
      if i = 0 then
        LmStringContainer := LmStringContainer + mStringContainer.Lines[i]
      else
        LmStringContainer := LmStringContainer + ';' + mStringContainer.Lines[i]

    end;
    LStrList.Add('mStringContainerTranslated.Lines='+LmStringContainer);
    LStrList.SaveToFile(FilePath);
  finally
    FreeAndNil(LstrList);
  end;
end;

Procedura LoadLang służy do wczytywania podanego ścieżką pliku języka.

procedure LoadLang(FilePath: TFileName = 'Polski.Klang');
begin
  if FilePath = '' then Exit;
  SetComponentPropertiesINIForForm(FilePath, self,
    {VarArrayOf(['Caption', 'Hint', 'Title', 'Text'])}null, akAccept );
  if Assigned(FStringContainer) then
  begin
    FStringContainer.Clear;
    FStringContainer.AddStrings(mStringContainer.Lines);
  end;
  if Assigned(FStringContainerTranslated) then
  begin
    FStringContainerTranslated.Clear;
    FStringContainerTranslated.AddStrings(mStringContainerTranslated.Lines);
  end;
end;

Translate jest ważną funkcją pozwalającą wstawiać tekst w kod, a nie tylko w komponenty. Częściowo zasada została wytłumaczona wyżej, ale wytłumaczę co to są FStringContainer, FStringContainerTranslated. Obie zmienne to TStringList które mają identyczne Lines z komponentów TMemo leżących na formatce (FStringContainer z mStringContainer, FStringContainerTranslated z mStringContainerTranslated). Pierwsza zawiera zapisane przez nas teksty które wykorzystujemy w kodzie, a druga tłumaczenia załadowane w pliku.

function Translate(StrIn: string): string;
var
  i: Integer;
  Lstr: string;
  LMin: Integer;
begin
  Result := StrIn;
  LMin := min (FStringContainer.Count, FStringContainerTranslated.Count);
  for i:= 0 to LMin - 1 do
  begin
    Lstr := Trim(FStringContainer[i]);
    if (Lstr <> '') and (Lstr = Trim(StrIn)) then
    begin
      Result:= FStringContainerTranslated[i];
      Break;
    end;
  end;
end;

Jak poniższy kod wykorzystać w naszym programie?
Do naszego projektu załączamy plik PropertiesIni.pas. Ustawiamy globalną zmienną widzianą w całym programie do której przypisana jest ścieżka naszego języka niech będzie to FJezykSciezkaPliku: string;. Gdzieś w programie zamieszczamy możliwość wyboru języka i ustawiamy ścieżkę do FJezykSciezkaPliku. W każdej formatce która ma być tłumaczona piszemy

procedure .FormCreate(Sender: TObject);
begin
  FStringContainer := TStringList.Create;
FStringContainerTranslated := TStringList.Create;
  if FJezykSciezkaPliku <> '' then
    LoadLang(FJezykSciezkaPliku);
end;

procedure .FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if FZapiszJezykPodstawowy then
  begin
    if not FileExists(ExtractFilePath(Application.ExeName)+'\language\zapis_formatek\') then
        ForceDirectories(ExtractFilePath(Application.ExeName)+'\language\zapis_formatek\');
    SaveLang(ExtractFilePath(Application.ExeName)+'\language\zapis_formatek\'+Self.Name + '_'+'Polski.Klang');
  end;
end;

procedure .FormDestroy(Sender: TObject);
begin
FreeAndNil(FStringContainer);
FreeAndNil(FStringContainerTranslated);
  if FZapiszJezykPodstawowy then
  begin
    if not FileExists(ExtractFilePath(Application.ExeName)+'\language\zapis_formatek\') then
        ForceDirectories(ExtractFilePath(Application.ExeName)+'\language\zapis_formatek\');
    SaveLang(ExtractFilePath(Application.ExeName)+'\language\zapis_formatek\'+Self.Name + '_'+'Polski.Klang');
  end;
end;

FZapiszJezykPodstawowy jest to również zmienna globalna, która określa czy po zamknięciu bądź zniszczeniu formatki mają zostać zapisane do pliku wszystkie kontrolki z tekstem. Procedura zapisu języka musi być tak umieszczona w kodzie aby dało się ją szybko wyłączyć. Robimy tak ponieważ służy ona tylko jednorazowo w celu zapisu wszytkich kontrolek z tekstem do pliku. Najpierw ustawiamy FZapiszJezykPodstawowy na True, kompilujemy i uruchamiamy. Po uruchomieniu programu otwieramy wszystkie formatki i zamykamy. Wtedy w folderze „\language\zapis_formatek\” zapisane są pliki z nazwami formatek w których są interesujące nas dane. Tworzymy nowy plik z nazwą naszego języka i wklejamy po kolei każdą z formatek. Tak zapisany plik możemy zastosować do tłumaczenia.

Chciałem nadmienić, że najlepszym sposobem jest utworzenie formatki wzorcowej z całą tą obsługą, a kolejne formy powinny dziedziczyć po wzorcu. Unikniemy wtedy powielania kodu na wszystkich formatkach, a w razie zmiany nie musimy dokonywać ich we wszystkich formatkach. Wzorcowa formatka jest do pobrania na końcu artykułu.

Obsługa programu Kuty Language Changer.

1. Najpierw wczytujemy plik, który utworzyliśmy z zapisanych wszystkich formatek. Będzie to nasz bazowy język.

2a. Jeżeli wcześniej zapisaliśmy jakieś tłumaczenie to możemy je odczytać i edytować.

2b. Jeżeli jest to nasze pierwsze tłumaczenie dodajemy nowe tłumaczenie. Podajemy nazwę i wciskamy Ok.

3. Na górze mamy box-a Select Form w którym wybieramy formatkę, którą chcemy tłumaczyć. Na grid-zie wpisujemy nasze tłumaczenie i przechodzimy po kolei.

Obok wyboru formatki mamy możliwość zaznaczenia dwóch checkbox-ów.

„Show only suplemented” – po zaznaczeniu pokazuje tylko kontrolki, które mają uzupełniony tekst. Jest to przydatne w sytuacji gdy mamy dużo kontrolek które nie mają ustawionych np. hintów więc nie ma potrzeby ich wyświetlać.

„Refresh application” – jeżeli obsłużymy tą opcję w naszej aplikacji (opis jak to zrobić w osobnym artykule) to po włączeniu jej, po zmianie tłumaczenia, informacja zostanie wysłana do aplikacji którą tłumaczymy i komponent zmieni właściwość na tą z tłumaczenia. Jedyne ograniczenie jest dla długości znaków. Powyżej 120 znaków property się nie zmieni. Jest to przydatne ponieważ na żywo widzimy jak nasza aplikacja wygląda po przetłumaczeniu i czy nowy ciąg znaków mieści się w kontrolce. Dodatkowo jeżeli ilość znaków tłumaczenia przekroczy ilość z wersji podstawowego języka, pole zostanie podświetlone kolorem czerwonym.

4. Jeżeli jest to długi tekst (np. przełamany enterami) możemy go edytować za pomocą tej opcji. Najczęściej jest wykorzystywane do edytowania tekstu z mStringContainerTranslated, które pozwala na tłumaczenie tekstu znajdującego się w kodzie.

5. Możemy również dodawać ręcznie nowe property (w sytuacji niedodania się automatycznie, w sytuacji wcześniejszego usunięcia itp.)

Teraz opiszę pozostałe opcje.

„Delete item” powoduje usunięcie aktywnej pozycji.

„Del unaccompanied” usuwa wszystkie puste property.

„Clear all list” powoduje wyczyszczenie listy. Można na nowo wczytywać i tłumaczyć.

„Save translation” powoduje zapisanie aktualnego tłumaczenia.

Po naciśnięciu przycisku „Start” rozpoczyna się odczyt kontrolek spod kursora myszy. Informacje pojawiają się w „Control Name” i „Control text”.

Liczę, że w miarę jasno wytłumaczyłem w jaki sposób wykorzystywać program do własnych celów.

Poniżej zamieszczam linki do kodów źródłowych

Pobierz: Kuty Language Demo

Przejdź do sekcji Pobieranie i pobierz program.

Aplikacja wykorzystuje mechanizm komunikacji między aplikacjami. Przeczytaj Artykuł: Komunikacja między aplikacjami aby wiedzieć jak wykorzystać tą funkcjonalność. W skrócie: wystarzczy załączyć Unit KutyKomunikacjaAplikacji.pas do projektu której główna forma nazywa się frmGlowny i obsługa jest zrobiona. W Kuty Language Changer zaznaczamy checkbox-a Refresh Application, uruchamiamy swoją aplikację i w momęcie wpisania tłumaczenia dla danej kontrolki automatycznie zostanie ta kontrolka odświeżona. Jest to przydatne, ponieważ od razu widzimy jak aplikacja jest tłumaczona i czy dane tłumaczenie mieści się w kontrolce z pierwotnym tekstem.

10 Odpowiedzi

  1. abbiemiranda@gmail.com

    It’s great that you are getting ideas from this piece of writing as well as from our argument made at this place.

  2. Katherin Branham

    Hello there! I just would like to give you a huge thumbs up for your great info you have here on
    this post. I am returning to your blog for more soon.

  3. Julia Tejeda

    Good post. I learn something totally new and challenging on sites
    I stumbleupon on a daily basis. It will always
    be interesting to read through articles from other authors and practice a little something from their web sites.

  4. leviphilips@live.de

    Everyone loves it when individuals get together and share views.

    Great site, continue the good work!

  5. Marcel Loy

    Hey! This is kind of off topic but I need some guidance from an established blog.
    Is it very difficult to set up your own blog? I’m not very techincal but I can figure things out pretty quick. I’m thinking about setting up my own but I’m not sure where to start. Do you have any ideas or suggestions? Thank you

  6. suv rental florida

    Hello there! This article couldn’t be written any better! Looking through this post reminds me of my previous roommate! He constantly kept talking about this. I’ll forward
    this information to him. Pretty sure he’s going to have a great read. Thanks for sharing!

  7. Markus Bourque

    You made some really good points there. I looked on the net for additional information about the
    issue and found most people will go along with your views on this site.

  8. alexander mangum

    Hi there I am so delighted I found your site, I really
    found you by error, while I was searching on Yahoo for something
    else, Anyhow I am here now and would just like to say
    cheers for a tremendous post and a all round exciting blog (I also love the theme/design),
    I don’t have time to read through it all at the moment but I have book-marked it and also included your RSS feeds, so
    when I have time I will be back to read a great deal more, Please do keep up the awesome
    jo.

  9. melba-mills@gawab.com

    It’s a shame you don’t have a donate button!
    I’d without a doubt donate to this fantastic blog! I suppose for now i’ll settle for book-marking
    and adding your RSS feed to my Google account. I look forward
    to fresh updates and will share this blog with my Facebook group.
    Talk soon!

  10. After looking into a number of the blog articles on your web site, I truly like your way of
    blogging. I book marked it to my bookmark webpage list and will be
    checking back in the near future. Please check out my website as well and let me know your opinion.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Możesz użyć następujących tagów oraz atrybutów HTML-a: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>