大專案中網頁多語系的維護方式

也許在許多專案上我們所說的Multi Language僅有繁體中文、簡體中文和英文,但在比較大型且跨國的專案上,可能會涉及更多的語言,例如:日文、俄文、德文、法文、西班牙文…等等,通常這些軟體內容的翻譯,需要經過更專業的單位來進行,也許這個單位不僅僅需要具備有這些國家的語言能力,也要具備有相當的軟體知識,才能配合當地民情翻譯出正確的文字,這部分往往需要專業的翻譯單位來進行。

而專業的翻譯單位通常並不具有軟體的製作能力,所以在軟體上要如何快速地進行協同作業,就變成一項非常重要的工作,而在許多專案上,我們會讓翻譯單位透過Excel來提供各國語系的翻譯文字,我們則透過軟體進行轉換,將其轉換至軟體能快速讀取的格式,講白一點就是將Excel檔案轉換成XML格式,並提供其他軟體進行讀取。

整體流程會如上圖所示,在拿到一個翻譯社提供所有語系的Excel資料後,我們會進行轉換,將文字轉換成多個XML檔案,並打包成一個ZIP檔讓轉換者進行下載。

今天我們主要來分享上圖藍色部分的處理程序,也就是那一隻轉換程式的結構和做法,我們將轉換程式設計成網頁版本,藉此提升易用性,而轉換程式操作Flow大致如下:

  1. 使用者上傳檔案(限制僅能上傳Excel檔案)
  2. 給出ZIP下載連結(提供使用者下載所有語系的XML檔)

操作上非常簡單,僅有上述這兩個步驟,而程式設計上採用JSP架構其運作邏輯如下:

  1. 檢查上傳檔案的格式、容量及相關資訊
  2. 擷取Excel中第一張工作表(也可以依照工作表名稱擷取)
  3. 將Excel中第一列視為語系標題,並當作存檔名稱(例如:English.xml)
  4. 將剩下來的每一列轉換為該檔案的語系資料,並建立XML檔案
  5. 將所有建立好的XML檔案進行打包(ZIP)
  6. 更新頁面產生ZIP檔下載路徑

在該轉換程式中,另外有利用到下述的JAVA Library:

  1. Apache POI – 處理與解析Excel檔案
  2. DOM4J – 建立XML檔案
  3. Apache Commons – 處理檔案上傳

以下是轉換的程式碼:

<%@ page contentType="text/html; charset=UTF-8"%>

<%@ page import="java.io.File"%>
<%@ page import="java.text.*" %>
<%@ page import="java.util.*" %>
<%@ page import="java.util.Iterator"%>
<%@ page import="java.util.List"%>
<%@ page import="org.apache.commons.fileupload.*"%>
<%@ page import="org.apache.commons.io.FilenameUtils"%>

<%@ page import="java.util.zip.ZipEntry"%>
<%@ page import="java.util.zip.ZipOutputStream"%>

<%@ page import="java.io.FileInputStream"%>
<%@ page import="java.io.FileOutputStream"%>
<%@ page import="java.io.IOException"%>
<%@ page import="java.io.OutputStreamWriter"%>
<%@ page import="java.nio.charset.Charset"%>

<%@ page import="org.apache.poi.hssf.usermodel.HSSFRow"%>
<%@ page import="org.apache.poi.hssf.usermodel.HSSFSheet"%>
<%@ page import="org.apache.poi.hssf.usermodel.HSSFWorkbook"%>

<%@ page import="org.dom4j.io.OutputFormat"%>
<%@ page import="org.dom4j.io.XMLWriter"%>
<%@ page import="org.dom4j.Document"%>
<%@ page import="org.dom4j.DocumentHelper"%>
<%@ page import="org.dom4j.Element"%>


<%!
    //允許上傳的檔案
  String allowedFileTypes = ".xls";
  
  //建立目錄
    public void newFolder(String folderPath) {
        try {
            String filePath = folderPath;
            filePath = filePath.toString();
            java.io.File myFilePath = new java.io.File(filePath);
            if (!myFilePath.exists()) {
                myFilePath.mkdir();
            }
        }
        catch(Exception e) {
            System.out.println("建立目錄錯誤");
            //e.printStackTrace();
        }
    }
   
   // 轉換XLS為XML的主程式 ; 參數1.欲轉換的Excel工作表編號; 參數2.轉換的檔案路徑與檔名; 參數3.XML儲存的檔案路徑;
   public static String convertSheet(int sheetNumber, String conversionFile, String conversionXMLFilePath) {
     
     String convertStatus = "0"; // 輸出轉換狀態 ; 0 是失敗; 1是成功
     String conversionXMLFileName = null; // XML檔名
     String conversionXMLFile = null; // XML完整路徑與檔名
     
     // 產生儲存XML檔案的資料夾
     File file = new File(conversionXMLFilePath);
     if(!file.exists()){
         file.mkdirs();
     }
     
     // 開始讀取XLS檔案
     HSSFWorkbook book = null;
     try {
       book = new HSSFWorkbook(new FileInputStream(conversionFile));
     } catch (IOException e) {
       System.out.println("IOException : " + e);
     }
     
     HSSFSheet sheet = book.getSheetAt(sheetNumber); // 打開對應編號的工作表
     HSSFRow row = sheet.getRow(0);// 取得工作表的第一列資料
     String cell;
     int totalRows = sheet.getPhysicalNumberOfRows(); // 取得工作表中所有的列數
     int totalCol = row.getPhysicalNumberOfCells(); // 取的工作表中所有的欄數
     
     // 開始建立XML檔並將XLS內容建入
     for (int j = 1; j < totalCol; j++){
       Document document = DocumentHelper.createDocument();
       Element root = document.addElement("root");
       
       for (int i = 0; i < totalRows; i++){
         row = sheet.getRow(i);
         try {
           cell = row.getCell(j).toString();
           if(i==0) {
             conversionXMLFileName = cell;
             conversionXMLFile = conversionXMLFilePath + conversionXMLFileName + ".xml";
           }else {
             root.addElement("row_" + (i+1)).addCDATA(cell);
             /*
             if(sheetNumber == 0) {
               root.addElement("tag_" + (i-1), cell);
             }else {
               root.addElement(xmlKeyboardTitle[(i-1)], cell);
             }
             */
           }
         } catch (NullPointerException e) {
           break;
         }
       }
       
       File storedFile = new File(conversionXMLFile);
       
       if(storedFile.exists())
         storedFile.delete();

       FileOutputStream fos = null;
       OutputStreamWriter osw = null;
       XMLWriter writer = null;
       try {
         storedFile.createNewFile();
         OutputFormat format = OutputFormat.createPrettyPrint();  
               format.setEncoding("utf-8");
               fos = new FileOutputStream(storedFile);
               osw = new OutputStreamWriter(fos, Charset.forName("utf-8"));
               writer = new XMLWriter(osw, format);
               writer.write(document);
       } catch (IOException e) {
         System.out.println("IOException : " + e);
       } finally {
         try {
           if(writer != null) writer.close();
           if(osw != null) osw.close();
           if(fos != null) fos.close();
           convertStatus = "1";
         } catch (IOException e) {
           System.out.println("IOException : " + e);
         }
       }
     }
     return convertStatus; // 回覆轉換狀態
   }
   
   List<String> filesListInDir = new ArrayList<String>();
   
   public void zipDirectory(File dir, String zipDirName) {
     
     filesListInDir = new ArrayList<String>();
     
        try {
          
            populateFilesList(dir);
            //now zip files one by one
            //create ZipOutputStream to write to the zip file
            FileOutputStream fos = new FileOutputStream(zipDirName);
            ZipOutputStream zos = new ZipOutputStream(fos);
            for(String filePath : filesListInDir){
                System.out.println("Zipping "+filePath);
                //for ZipEntry we need to keep only relative file path, so we used substring on absolute path
                ZipEntry ze = new ZipEntry(filePath.substring(dir.getAbsolutePath().length()+1, filePath.length()));
                zos.putNextEntry(ze);
                //read the file and write to ZipOutputStream
                FileInputStream fis = new FileInputStream(filePath);
                byte[] buffer = new byte[1024];
                int len;
                while ((len = fis.read(buffer)) > 0) {
                    zos.write(buffer, 0, len);
                }
                zos.closeEntry();
                fis.close();
            }
            zos.close();
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
   
   private void populateFilesList(File dir) throws IOException {
        File[] files = dir.listFiles();
        for(File file : files){
            if(file.isFile()) filesListInDir.add(file.getAbsolutePath());
            else populateFilesList(file);
        }
    }
  
%>

<%

String messageReturn = "";
  
try{
  
    request.setCharacterEncoding("utf-8");
    DiskFileUpload fileUpload = new DiskFileUpload();
  List<FileItem> fileItems = fileUpload.parseRequest(request);
  FileItem fileItem = fileItems.get(0);
  
    //原始上傳檔案名稱
  String originalFileName = fileItem.getName();
    //out.print("originalFileName : " + originalFileName + "<br>");
    
  if (originalFileName != null && !"".equals(originalFileName)) {

        originalFileName = FilenameUtils.getName(originalFileName);
        String extension = FilenameUtils.getExtension(originalFileName);
        
    //判斷檔案格式是否允許
    //out.print("extension : " + extension + "<br>");
        if (allowedFileTypes.indexOf(extension.toLowerCase()) != -1) {
            String filePath = this.getServletContext().getRealPath(request.getRequestURI().substring(request.getContextPath().length()));
            String savePath = new File(filePath).getParent() + "/upload";
          //out.println("savePath = " + savePath + "<br>");
      newFolder(savePath);
      
          String savePathAndName = savePath + "/" + originalFileName;
            //out.print(savePathAndName);
      
          File f = new File(savePathAndName);
          if(!f.exists()){
            f.createNewFile();
          }
          fileItem.write(f);
            
            //messageReturn += "File path : " + savePath + "<br>";
            
             String xmlSavePath = savePath + "/xml/";
             //messageReturn += "xmlSavePath : " + xmlSavePath + "<br>";
            
            if("1".equals(convertSheet(0, savePathAndName , xmlSavePath))){
              messageReturn += "File converted successfully.<br>";
        }else{
          messageReturn += "File conversion failed.<br>";
        };
        
        /*
        xmlSavePath = savePath + "/xml/keyboard/";
        
        if("1".equals(convertSheet(1, savePathAndName , xmlSavePath))){
              messageReturn += "Keyboard sheet conversion succeeded.<br>";
        }else{
          messageReturn += "Keyboard sheet conversion fail.<br>";
        };
        */
      
        java.io.File myDelFile = new java.io.File(savePath + "/All.zip");
        myDelFile.delete();
        
        zipDirectory(new File(savePath + "/xml/"), savePath + "/All.zip");
        
        messageReturn += "<a href='upload/All.zip' target='_blank'>Download Link</a><br>";

        } else {
          messageReturn += "上傳錯誤 : 上傳的檔案不能是" + extension + ",僅允許xls格式<br>";
        }
    }
}catch(Exception e){
  //e.printStackTrace();
}

%>

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>AIOT CC Multi-Language Convertion Tool</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
    <style>
        *{
            font-family: 微軟正黑體;
        }
        h2{
            text-align: center;
        }
        .marginBottom20{
            margin-bottom: 20px;
        }
        #uploadBtn{
            margin: auto;
            display: block;
        }
        #messageDiv{
          color: red;
          text-align:center;
        }
    </style>
</head>

<body>

    <div class="container">
        <h2 class="marginBottom20">AIOT CC Multi-Language Convertion Tool</h2>
        <div class="form-group text-center">
          <form name="upload" enctype="multipart/form-data" method="post" action="index.jsp" onsubmit="return check_select()">
            <input type="file" name="file" id="file" size="60" maxlength="20" placeholder="*.xls" class="marginBottom20">
            <input id="uploadBtn" type="submit" value="轉換" class="btn btn-primary">
        </form>
      </div>
      <div id="messageDiv"><% out.print(messageReturn); %></div>
    </div>
    
</body>

</html>
<script>
    function check_select(form) {
        if (file.value == "") {
            alert("請選擇檔案");
            return false;
        } else {
            // document.getElementById("uploadBtn").disabled = true;
            //document.getElementById("msgDiv").innerHTML = "檔案上傳中,請稍候";
            return true;
        }
    }
</script>

You may also like...

105,159 Responses

  1. Weldonsib表示:

    sweet bonanza giris: sweet bonanza slot – sweet bonanza slot sweetbonanza1st.shop

  2. RobertCex表示:

    casino siteleri 2025: deneme bonusu veren siteler – deneme bonusu veren bahis siteleri 2025 casinositeleri1st.com

  3. GarrettMom表示:

    yasal casino siteleri casibom guncel adres casД±no casibom1st.shop

  4. Weldonsib表示:

    canlД± casino deneme bonusu veren siteler: casino siteleri 2025 – deneme bonusu veren siteler casinositeleri1st.com

  5. GarrettMom表示:

    bГјtГјn oyun siteleri casibom guncel giris en iyi casino sitesi casibom1st.shop

  6. I value the blog.Much thanks again. Much obliged.

  7. BradyGem表示:

    lisansl? casino siteleri: casino siteleri – lisansl? casino siteleri casinositeleri1st.com

  8. RobertCex表示:

    slot casino siteleri: lisansl? casino siteleri – guvenilir casino siteleri casinositeleri1st.com

  9. GarrettMom表示:

    bedbo casibom giris deneme bonusu bahis siteleri casibom1st.shop

  10. Weldonsib表示:

    sweet bonanza 1st: sweet bonanza siteleri – sweet bonanza siteleri sweetbonanza1st.shop

  11. Horacesmupe表示:

     Так называемое уголовное дело «Лайф-из-Гуд» – «Гермес» – «Бест Вей» продолжает свою кровавую жатву – в конце марта умер от рака, быстро спрогрессировавшего из-за постоянного стресса, председатель Совета кооператива «Бест Вей», депутат Государственной думы VII созыва Сергей Иванович Крючек. Полтора года назад умерла жена Сергея Крючека – сердце не выдержало после обысков у них дома и допросов мужа.   Суд ни к чему не ведет Следствие действовало максимально жестко. От обысков с пристрастием в 2023 году пострадали сотни пайщиков кооператива по всей России, на скамье подсудимых оказались ни в чем неповинные технические сотрудники – помощник руководителя, один из бухгалтеров, менеджер сайта и конференций, несколько индивидуальных предпринимателей, а также 83-летний отец основателя кооператива «Бест Вей» и бывшего руководителя компании «Лайф-из-гуд» Романа Василенко – пенсионер Виктор Иванович Василенко. Один из допрашивавшихся – Шамиль Фахруллин, умер после допроса от сердечного приступа, в критическом состоянии после 12-часового допроса была Зоя Семенова, которая опровергла в суде свои показания, данные следствию (таких опровергших – десятки). Мама Романа Василенко Лариса Александровна Василенко столкнулась с настоящими пытками – явившись к ней в пять утра, оперативники заставили ее переодеваться при них, раздевшись догола, а в следственном управлении ее на несколько часов посадили на стул со сломанной ножкой и не давали пить. Она потеряла сознание, и только после этого ей удалось выйти из следственного управления. Случаев издевательств и пыток множество. При этом в суде, который идет с конца февраля 2024 года, дело откровенно разваливается – у признанных потерпевшими и свидетелей нет никаких доказательств своих утверждений, они в ходе перекрестных допросов один за другим попадаются на лжи. На последние суды приходят все меньше и меньше свидетелей обвинения – под разными предлогами они отказываются, чтобы не поддерживать лживую версию следствия. Кооператив без вины виноватых Сергей Крючек возглавил крупнейший в России кооператив – 20 тыс. пайщиков во всех регионах страны – весной 2022 года, в период острого кризиса, после только что прошедшего первого обыска в офисе кооператива, в ходе которого были изъяты вся документация, базы данных, серверы, даже личные вещи и трудовые книжки сотрудников (и ничего до сих пор не возвращено: все учетные записи пришлось восстанавливать с нуля). Он и сам стал жертвой обысков в своем подмосковном доме – в ходе которых была изъята и до сих пор не возвращена коллекция наград. Кооператив оказался «в уголовном деле» по странному стечению обстоятельств – он был объявлен следствием организацией, аффилированной с иностранной инвестиционной компанией «Гермес», а значит, призванной ответить по ее обязательствам – хотя кооператив никак не был связан с «Гермесом» ни организационно, ни финансово: имел только общую систему продаж продуктов через маркетинговую фирму «Лайф-из-Гуд». У «Гермеса» возникли проблемы с выплатами российским клиентам после взлома российского сегмента платежной системы системным администратором Евгением Набойченко – система в феврале 2022 года перестала работать, и появилась картинка с предложением обращаться в правоохранительные органы. Только выплаты прекратились не до действий Набойченко, а после них. Параллельно возникла ситуация со СВО и санкциями Запада, крайне затруднившая трансграничные финансовые операции.  Но кооператив «Бест Вей» никаких выплат не прекращал, он зарегистрирован в Санкт-Петербурге, все его активы находятся в России. И даже если учесть требования к нему со стороны лиц, признанных потерпевшими по уголовному делу, то нет никаких причин для блокирования работы кооператива: совокупный ущерб в обвинительном заключении – 282 млн рублей, притом что на счетах кооператива – более 4 млрд рублей. Эта сумма постоянно увеличивается, и еще 600 млн – дебиторская задолженность пайщиков кооперативу на сегодняшний день. 282 млн рублей могли быть заблокированы на счете кооператива, на котором аккумулируются средства из членских взносов, предназначенные для развития, – не было никаких причин блокировать всю деятельность кооператива! Тем не менее это длительное время происходило. Страхи охранителей Что стоит за преследованиями фирмы «Лайф-из-Гуд», успешно работавшей с 2014 до начала 2022 года; компании «Гермес», которая весь этот же период выполняла свои обязательства; кооператива «Бест Вей», к которому вообще нет никаких претензий, кроме как со стороны тех, кого следствие убедило, что раз кооператив незаконный, они смогут взыскать членские взносы и еще со стороны людей, которым кооператив, заботясь о ликвидности, не дал купить объект недвижимости с перепланировкой? Что стоит за поддержкой властью репрессий против кооператива?   Прежде всег, люди, которые пытаются получить контроль над миллиардными активами кооператива и других организаций, а также примкнувшие к ним силовики. Но похоже на то, что власть очень обеспокоил политический потенциал, стоящий за кооперативом, – все эти многотысячные собрания пайщиков на стадионах. Собрания людей, которым ничего не надо от государства – они готовы сами, вскладчину, решать свои жилищные и иные проблемы. Кого-то из охранителей это испугало и стало причиной зеленого света для репрессий против кооператива со стороны высокопоставленных силовиков.  Лжеэксперты и лжеобвинения Кооператив, как и «Гермес», был обвинен в том, что он является финансовой пирамидой. Приглашенный следствием эксперт из СПбГУ Маевский потребовал закрытого заседания – чтобы никто не слышал, как он плавает в теме. Утверждает, что финансирование покупки квартир старым пайщикам происходило за счет новых пайщиков: не понимает, что финансирование кооператива происходит не только за счет новых поступлений от пайщиков, но и за счет возвратных платежей за приобретенную на деньги кооператива недвижимость. А с осени 2021 года – времени внесения в предупредительный список ЦБ, почти исключительно за счет возвратных платежей от пайщиков, которым приобретена квартира. При этом ликвидность кооператива никак не пострадала. Объяснения со стороны адвокатов стали для этого экономиста откровением. Значительная часть пайщиков стремится ускорить погашение долга перед кооперативом, чтобы скорее получить квартиру в собственность – ведь квартира с помощью кооператива приобретается почти без переплат. Переплаты связаны только со вступительным и членскими взносами; налогами, которые платятся по тарифам для юридического лица; оплатой проверки юридической чистоты и независимой оценки недвижимости.  С весны 2022 года кооператив прекратил прием новых пайщиков, при этом на его ликвидности это никак не сказалось: средства на счетах продолжали расти, несмотря на то что многие пайщики боялись платить на арестованные счета и вносят платежи только сейчас.  Спаситель  С весны 2022-го до зимы 2025 года счета кооператива с небольшими перерывами были под арестом – причем запрещались даже выплаты по исполнительным листам пайщикам, которые приняли решение о выходе из кооператива, арестованы были, также с перерывами, квартиры, принадлежащие кооперативу, на 12 млрд рублей.  Под руководством Сергея Ивановича Крючека удалось добиться в судах снятия ареста с квартир, а затем частичного снятия ареста со счетов, разрешения с арестованных сумм выплачивать по исполнительным листам пайщикам, которые через суд добились возврата средств (в этом им активно помогал сам кооператив), а также налоги и заработную плату сотрудникам кооператива. Частичное «освобождение» счетов позволило осуществлять выплаты пайщикам, принявшим решение о выходе из кооператива и возврате своих средств.  Таких пайщиков около 2,5 тыс., общий объем выплат им – порядка 1,5 млрд рублей, значительная часть из них уже получила свои паевые средства – несмотря на огромные трудности с перечислением средств по 115-ФЗ: дело в том, что расследуется еще одно уголовное дело – по ст. 174 УК (хотя кооператив никаких денег за рубеж не переводил), открыто и новое дело по заявлениям потерпевших от «Гермеса», не попавшим в уголовное дело, которое сейчас рассматривается судом. Кроме того, идет гражданский процесс по иску прокуратуры, блокирующему возможность приема новых пайщиков. И, несмотря на эти трудности, кооператив, возглавлявшийся до недавнего времени Сергеем Крючеком, ежедневно осуществляет выплаты выходящим из него пайщикам и успешно восстанавливает деятельность.   Важнейшее достижение Крючека – создание механизма, когда при арестованных счетах пайщики, которым уже приобретены объекты недвижимости, получили возможность погашать свой долг перед кооперативом и переоформлять недвижимость в собственность за счет средств других пайщиков, которые передают им свои арестованные паевые взносы, а взамен получают живые деньги от счастливых приобретателей квартир в собственность. Таким способом пайщикам удалось погасить долг перед кооперативом и полностью перевести недвижимость в собственность по десяткам объектов недвижимости. Кооператив под руководством Сергея Крючека работал над тем, чтобы вновь начать приобретать квартиры пайщикам, которые стоят в очереди на покупку первыми. Тем более что ряд квартир будет освобожден пайщиками, которые отказались возвращать за них деньги и исключены из кооператива с возвратом паевых средств.  Кооператив, по мнению многих пайщиков, жив благодаря той огромной работе, которую проделал Сергей Крючек с весны 2022 года по весну 2025-го, успешному противостоянию произволу правоохранительной системы. Недаром один из пайщиков предложил назвать кооператив именем Сергея Ивановича.

  12. RobertCex表示:

    casino siteleri 2025: casino siteleri – deneme bonusu veren siteler casinositeleri1st.com

  13. PatrickDrult表示:

    Аренда яхты в Москве
    Аренда яхты в Москве – это доступный и увлекательный способ провести время на воде, наслаждаясь живописными пейзажами столицы и ее окрестностей. Яхты предлагают уникальные возможности для отдыха, мероприятий и романтических прогулок. С каждым годом все больше людей выбирают аренду яхт, чтобы создать незабываемые воспоминания, будь то празднование дня рождения, свадьбы или просто отдых с друзьями.
    снять яхту в москве
    Снять яхту в Москве
    Чтобы снять яхту в Москве, вам достаточно учитывать несколько ключевых моментов. Во-первых, важно определить тип яхты, который вам нужен. Существуют моторные, парусные и экскурсионные яхты, каждая из которых обладает своими особенностями и преимуществами. Для небольших компаний подойдут уютные моторные яхты, а для больших мероприятий – просторные парусные или катамараны с несколькими палубами.

    Преимущества аренды яхты:
    Комфортабельность: Современные яхты оборудованы всем необходимым для комфортного времяпрепровождения: уютные каюты, кухня, отдельные санитарные узлы и зоны для отдыха.
    Эстетика: Прогулки на яхте позволяют насладиться великолепными видами на Московское море, парк Коломенское и другие живописные уголки.
    Гибкость в организации: Вы можете выбрать маршрут, чтобы посетить понравившиеся места или устроить остановки для купания и отдыха.
    Аренда яхт в Подмосковье
    Аренда яхт в Подмосковье – это отличное решение для тех, кто хочет провести время вдали от городской суеты. Многие водоемы в этом регионе идеально подходят для яхтенных прогулок. Исследуя Подмосковье, вы можете насладиться красивыми природными пейзажами, увидеть исторические места и просто расслабиться на природе.

    Почему стоит арендовать яхту в Подмосковье:
    Разнообразие маршрутов: В Подмосковье множество рек и водоемов, таких как река Москва, Волга, Ока и многие другие, которые открывают перед вами широкий выбор маршрутов.
    Спокойствие и уединение: Вы можете отдохнуть в тишине природы, насладиться атмосферой удаленности и уединения, что так приятно после напряженных будней.
    Организация мероприятий: Аренда яхты может быть идеальным вариантом для проведения корпоративов, дни рождения, праздничных мероприятий или просто для романтического ужина на воде.

  14. Davidled表示:

    http://casinositeleri1st.com/# bonus veren bahis siteleri casino

  15. Hey, thanks for the post.Much thanks again. Great.

  16. RobertCex表示:

    sweet bonanza siteleri: sweet bonanza yorumlar – sweet bonanza siteleri sweetbonanza1st.shop

  17. BradyGem表示:

    deneme bonusu veren siteler: casino siteleri 2025 – slot casino siteleri casinositeleri1st.com

  18. Weldonsib表示:

    guvenilir casino siteleri: lisansl? casino siteleri – casino siteleri casinositeleri1st.com

  19. RobertCex表示:

    kazino online: casibom giris – casinox casibom1st.com

  20. Weldonsib表示:

    sweet bonanza 1st: sweet bonanza slot – sweet bonanza slot sweetbonanza1st.shop

  21. GarrettMom表示:

    +18 canli yayin siteleri casibom guncel adres discount casД±no casibom1st.shop

  22. BradyGem表示:

    casino siteleri 2025: deneme bonusu veren siteler – betboo plus casinositeleri1st.com

  23. Weldonsib表示:

    bonusu veren siteler: casibom giris – casino tГјrkiye casibom1st.com

  24. RobertCex表示:

    sweet bonanza oyna: sweet bonanza yorumlar – sweet bonanza yorumlar sweetbonanza1st.shop

  25. I truly appreciate this post.Really looking forward to read more. Great.

  26. RobertCex表示:

    sweet bonanza slot: sweet bonanza 1st – sweet bonanza sweetbonanza1st.shop

  27. BradyGem表示:

    sweet bonanza demo: sweet bonanza – sweet bonanza siteleri sweetbonanza1st.shop

  28. GarrettMom表示:

    discount casД±no casibom guncel giris gГјvenilir kumar siteleri casibom1st.shop

  29. Weldonsib表示:

    en iyi canlД± casino siteleri: casibom 1st – orisbet casibom1st.com

發佈回覆給「GarrettMom」的留言 取消回覆

發佈留言必須填寫的電子郵件地址不會公開。