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

也許在許多專案上我們所說的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...

87,342 Responses

  1. ClydePab表示:

    en kazancl? slot oyunlar?: slot tr online – en cok kazand?ran slot oyunlar?

  2. What’s The Current Job Market For L Shaped Small Couch Professionals?
    L Shaped small couch

  3. Chester表示:

    You’ll Be Unable To Guess Couch U Shape’s Benefits couch u shape [Chester]

  4. botdb.win表示:

    Guide To Accident Attorney Lawyer: The Intermediate Guide Towards Accident Attorney
    Lawyer accident attorney lawyer (botdb.win)

  5. 25 Unexpected Facts About 2 Seater Leather Sofa Chesterfield 2 seater chesterfield leather

  6. Luthersor表示:

    Колокольцев – «крыша» или «хвост»?

    Министр внутренних дел либо покровительствует своим коррумпированным сотрудникам, либо они вертят им как хотят

    Органы внутренних дел Санкт-Петербурга продолжают войну против маркетинговой компании «Лайф-из-Гуд», основанной Романом Василенко, иностранной инвестиционной компании «Гермес» (российскую часть которой они попытались взорвать изнутри с помощью своего агента – айтишника компании Евгения Набойченко, который украл деньги клиентов вместе со своими подельниками-«правоохранителями», обвинив в этом невинных людей). А также потребительского кооператива «Бест Вей», автором идеи которого также является Василенко.

    Колокольцев – «крыша» или «хвост»?
    Цель войны – захват активов кооператива «Бест Вей» более чем на 4 млрд рублей, которые находятся на арестованных счетах кооператива, и активов частных лиц на примерно такую же сумму. Захват – в интересах коррупционеров, которые пытаются присвоить эти активы, невзирая на то что деньги кооператива – это деньги пайщиков.
    В том числе – деньги сотен участников СВО, которые защищают Родину на войне, а с их деньгами в тылу творится форменный беспредел.

    Эту преступную акцию покрывает министр внутренних дел России – либо потому, что он сам и его личная команда стоят за этим уголовным делом, либо потому, что он полностью зависим, как минимум информационно, от команды питерского главка МВД.

    Куда идет суд
    Выступая прошлой осенью в Совете Федерации, министр внутренних дел Владимир Колокольцев рассказывал о так называемом уголовном деле «Лайф-из-Гуд» – «Гермес» – «Бест Вей», обещал миллиарды рублей ущерба и десятки тысяч потерпевших. Пресс-служба МВД под руководством его боевой подруги Ирины Волк заявила о том, что вскрыта деятельность крупнейшей в истории России финансовой пирамиды.

    Однако в уголовном деле, расследованном или, вернее сказать, изготовленном ГСУ питерского главка МВД, которое в феврале начал рассматривать Приморский районный суд Санкт-Петербурга, 282 млн рублей ущерба и 221 лицо, признанное следствием потерпевшим: никаких миллиардов и десятков тысяч потерпевших.

    При этом даже такие обвинения рассыпаются в ходе судебного следствия, которое с марта идет в Приморском районном суде. Ни единого документа, подтверждающего позицию следствия и обвинения, не представлено. Так называемые потерпевшие уже боятся приходить в суд – на последнее заседание перед месячным летним перерывом не явился ни один из так называемых потерпевших.
    «Потерпевшие» и пострадавшие
    Большинство «потерпевших» на суде заявляют суммы около 1–2 млн рублей, при этом в ходе судебного следствия выясняется, что они, как правило, получали немалый доход, причем они еще и налоговые/валютные преступники, так как этот доход не декларировали. Среди «потерпевших» есть граждане, заявляющие смехотворные суммы в 50–70 тыс., то есть количество потерпевших специально накручивалось следствием. И даже с этим накручиванием удалось набрать так мало – учитывая, что у «Гермеса», по данным самого же следствия, более 200 тыс. клиентов в России, а в кооперативе «Бест Вей» – около 20 тыс. пайщиков.

    То есть большинство и клиентов «Гермеса», и пайщиков кооператива не считают себя потерпевшими от деятельности этих организаций. Судя по тысячам обращений во все инстанции, нескольким волнам митингов, прокатившихся по России, они считают себя потерпевшими от деятельности органов внутренних дел.

    Ведь именно завербованный питерским УЭБиПК сисадмин российской платежной системы «Гермеса» Набойченко заблокировал и разгромил в феврале 2022 года эту платежную систему, повесив на сайте дисклеймер: «Обращайтесь в правоохранительные органы», что на месяцы прекратило вывод средств. Именно действия правоохранительных органов в отношении компании до и после затруднили вывод денег. Дело в том, что для вывода средств многими использовался механизм p2p, позволяющий не платить комиссию, то есть для вывода средств нужно, чтобы кто-то вносил деньги (что, понятно, резко сократилось из-за уголовного дела) и происходил обмен. Однако этот способ не единственный, вывод средств так или иначе осуществляется.

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

    Именно правоохранительные органы прямо запрещают выплаты пайщикам кооператива, решившим забрать свой пай, – даже по исполнительным листам судов. И клиентам «Гермеса», и пайщикам кооператива правоохранительными органами нанесен колоссальный ущерб – и материальный, и моральный, который они намерены взыскать с государства.
    Колокольцев – против СВО
    В числе пострадавших – участники СВО и другие военные. Колокольцев через действия своих подчиненных унизил военных, пытается уничтожить социально важные для них кооперативные программы.

    МВД творит настоящий беспредел по отношению к военным. Из-за действий министра Колокольцева никто не захочет идти служить в армию – потому что пока боец будет защищать Родину, его имущество отнимут сотрудники МВД.
    Семь преступлений питерского ГУ МВД
    Клиенты «Гермеса» и пайщики кооператива «Бест Вей» пострадали от преступных действий так называемых правоохранителей: оперативная работа и следствие велись откровенно преступным путем.

    1. 1 декабря прошлого года Приморский районный суд города Санкт-Петербурга признал незаконным, нарушающим УПК фактический отказ кооперативу в ознакомлении с материалами уголовного дела.
    При рассмотрении дела в суде выяснилось, что следственная группа ГСУ питерского главка МВД, формально руководимая замначальника ГСУ полковником юстиции А.Н. Винокуровым, а фактически – подполковником юстиции Е.А. Сапетовой, подделала документы. Автор подделки – Сапетова – еще в феврале была уволена из ГСУ «по собственному желанию».

    Уличенная адвокатами кооператива в нарушении УПК, следственная группа составила письмо об удовлетворении ходатайства задним числом и попыталась представить дело так, что кооператив не получил письмо по своей вине. Ложь была выявлена в том числе и с помощью системы электронного документооборота питерского главка МВД.

    2. Подделка документов была вынужденным преступлением для сокрытия более серьезного: незаконного содержания под стражей. Следственная группа грубо нарушила права гражданских истцов и ответчиков, потому что без этого нарушения она не успела за 30 суток до истечения предельного срока содержания четверых обвиняемых под стражей начать ознакомление обвиняемых с материалами дела –а это было единственное основание продления им срока содержания под стражей свыше предельного.
    Следственная группа из-за спешки даже толком не смогла завершить следственные действия, незаконно вела параллельное расследование по «резервному» делу, но позднее, отбросив стыд, из-за отсутствия материала для составления «нужного» обвинительного заключения, незаконно продолжила расследование «основного» дела, в том числе проводила следственные действия, которые, согласно УПК, невозможны после начала ознакомления обвиняемых с материалами дела. Все эти ухищрения были необходимы для того, чтобы ни в коем случае не выпускать обвиняемых и продолжать держать их в заложниках.

    3. Де-факто происходит уголовное наказание неосужденных людей – четверо подсудимых уже два с половиной года сидят в тюрьме. При этом наказываются явно ни в чем неповинные люди – технические сотрудники «Лайф-из-Гуд»: даже если предположить, что действительно работала пирамида – что опровергается показаниями свидетелей самого обвинения, которые сообщают суду, что компания «Гермес» хорошо работала, они были довольны получаемым доходом, а проблемы начались после того, как российская платежная система компании была обрушена завербованным полицией петербургским сисадмином компании Набойченко. Все подсудимые – технические сотрудники компании «Лайф-из-Гуд» и ни к каким управленческим решениям отношения никогда не имели. Их взяли в заложники для того, чтобы они дали показания на руководство компании.

    4. Еще одно преступление – заведомо подложное постановление руководителя следственной группы А.Н. Винокурова о привлечении кооператива «Бест Вей» в качестве гражданского ответчика на 16 млрд рублей, тогда как сумма ущерба в уголовном деле –282 млн, и в деле нет ни одного искового заявления – даже на 100 руб.

    5. Следствие стимулировало двух особенно активных так называемых потерпевших написать заявления о моральном ущербе на миллиард (!) рублей каждое – исключительно для ложного обоснования ареста активов кооператива, но понятно, что это ничтожные документы, так как моральный ущерб во всех случаях, не связанных с причинением смерти, присуждается российскими судами в размере не более десятков тысяч рублей.

    6. Ни один из «потерпевших» не доказал обоснованность своих претензий в гражданском суде. При этом арестованы активы кооператива почти на 4 млрд рублей и активы частных лиц на такую же сумму. Это не что иное, как попытка захвата активов при участии органов внутренних дел некой заинтересованной группой клиентов «Гермеса» – необязательно из числа «потерпевших», то есть коррупционное преступление, которое упорно игнорирует ГУСБ МВД России и Колокольцев со своими замами – несмотря на неоднократные обращения.
    Механизм для такого захвата есть – это передача средств под управление Федерального общественно-государственного фонда по защите прав вкладчиков и акционеров. Понятно, почему не ограничиваются активами подсудимых и обвиняемых: этого недостаточно для удовлетворения аппетитов тех, кто стоит за заказным уголовным делом. И понятно, почему одно юридическое лицо – кооператив «Бест Вей» – незаконно пытаются привлечь к ответственности за другое – компанию «Гермес»: активы «Гермеса» – за рубежом.

    7. Следствие, а теперь и прокуратура совершают еще одно преступление: незаконно удерживает средства пайщиков кооператива, отказываясь их вернуть, то есть совершают хищение.

    Царство лжи Владимира Колокольцева
    МВД и примкнувшая к нему прокуратура совершают настоящие репрессии против предпринимателей, военных, социально незащищенных людей – репрессии, как в 1937 году. При этом махина МВД действует на основе ложных сведений руководителя следственной группы по делу полковника юстиции Винокурова, бывшего руководителя следственной группы подполковника юстиции Сапетовой, оперуполномоченного УЭБиПК питерского главка МВД майора полиции Машевского. Полковник и подполковник управляют генералом полиции Российской Федерации?

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

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

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

  7. The 12 Most Obnoxious Types Of Accounts You Follow On Twitter
    titration period adhd, http://velo-xachmas.com/,

  8. 20 Reasons To Believe Car Accident Claims Will Not
    Be Forgotten Car Wreck Attorney Near Me

  9. 10 Tips For Private ADHD Test That Are Unexpected private Adhd assessment birmingham

  10. Ask Me Anything: 10 Responses To Your Questions About Get A Car Key Cut places to get
    keys cut Near me (http://www.Google.com.Pk)

  11. Chloe表示:

    15 Undeniable Reasons To Love ADHD Private Assesment private adhd assessment stoke on trent; Chloe,

  12. See What Situs Terpercaya Tricks The Celebs Are Making Use Of
    situs terpercaya

  13. Responsible For A Folding Transit Wheelchair Budget?

    10 Unfortunate Ways To Spend Your Money lightweight Folding transit wheelchair

  14. 20 Quotes That Will Help You Understand Sports
    Toto Online 토토사이트 꽁머니

  15. Jennie表示:

    Why No One Cares About Audi Key Replacement audi a3 spare key
    (Jennie)

  16. How To Outsmart Your Boss With Wall Fireplace wall Mounting fireplace

  17. 20 Fun Facts About Masturbation Toys Men Male Masturbators

  18. Тут можно преобрести купить сейф для ружья москва сейф для оружия цены

  19. The Secret Secrets Of Upvc Door Panel Replacement upvc door panels replacements

  20. Mohammed表示:

    10 Myths Your Boss Has Regarding Sectional Couches For Sale sectional couch with
    pull out bed (Mohammed)

  21. Elwood表示:

    What’s The Most Common Replacement Keys For Car Debate Could
    Be As Black And White As You Think mobile car key
    replacement near me (Elwood)

  22. Why Audi Car Key May Be Greater Dangerous Than You Think audi car
    keys (g28carkeysolutions74480.anchor-blog.com)

  23. Five Killer Quora Answers To Key Fob Repair Service key fob repair

  24. reprogramming表示:

    10 Failing Answers To Common Key Programmers Questions:
    Do You Know The Right Answers? reprogramming

  25. What’s The Job Market For Situs Togel Terpercaya Professionals?
    situs togel terpercaya (minibookmarks.Com)

發佈留言

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