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

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

103,786 Responses

  1. KennethKex表示:

    пин ап казино официальный сайт: pin up вход – пин ап вход

  2. Добро пожаловать в Jetton Casino – ваш личный мир развлечений и крупных выигрышей. Мы предлагаем захватывающий выбор игр, мгновенные выплаты и щедрые бонусы. Присоединяйтесь и получайте бонусы для успешного старта.

    Почему выбирают https://jetton-casinochampion.makeup/? Мы ценим каждого игрока и создаем лучшие условия для комфортной игры. Еженедельные турниры, программы лояльности и персональные подарки делают игру еще интереснее. Ваши финансы и личные данные в полной безопасности.

    Огромный выбор слотов, карточных игр и лайв-казино.
    Персональные предложения и приятные сюрпризы.
    Быстрое пополнение счета и моментальный вывод выигрышей.
    Захватывающие турниры с крупными призами и шанс сорвать джекпот.

    Испытайте удачу в Jetton Casino и станьте частью элитного клуба победителей.

  3. Richardfek表示:

    pin up casino pin up casino pin up az

  4. KODwbd表示:

    Онлайн чат с психологом без регистрации. Психолог t me. Получите консультацию онлайн-психолога в чате прямо сейчас.

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

  5. KennethKex表示:

    pin up az: pin up azerbaycan – pin up az

  6. ZackaryGer表示:

    http://pinuprus.pro/# пин ап зеркало

  7. F168表示:

    Thanks on your marvelous posting! I truly enjoyed reading it, you happen to be a great author.I will make sure to bookmark yourblog and will come back in the future. I want to encourage you to continue your great writing, havea nice weekend!

  8. You expressed it wonderfully!

  9. ZackaryGer表示:

    http://pinuprus.pro/# пинап казино

  10. Elmerbuils表示:

    пин ап зеркало: пин ап зеркало – pin up вход

  11. Richardfek表示:

    пинап казино пин ап вход пин ап зеркало

  12. ZackaryGer表示:

    http://pinuprus.pro/# пин ап зеркало

  13. ZackaryGer表示:

    https://pinupaz.top/# pin up az

  14. KennethKex表示:

    pin up casino: pin up azerbaycan – pin up

  15. Richardfek表示:

    pin up вход пин ап зеркало пин ап зеркало

  16. KennethKex表示:

    вавада зеркало: вавада – vavada casino

  17. ZackaryGer表示:

    https://vavadavhod.tech/# vavada вход

  18. Richardfek表示:

    pin up az pin up pin up casino

  19. KennethKex表示:

    вавада официальный сайт: вавада – vavada вход

  20. fuuslrpbq表示:

    Hydro X Series custom cooling helps push your system’s performance as far as it can go, while lowering temperatures and noise levels Get started creating your own experiences! Hydro X Series custom cooling helps push your system’s performance as far as it can go, while lowering temperatures and noise levels Compact ATX Mid-Tower Cases The Allure of Curved Glass Experiences Alienware GPUs, developed for high performance by our engineers to meet rigorous thermal standards, deliver up to 450W on Aurora and 600W on Area-51, ensuring maximum visual fidelity and seamless FPS for your entire game library. The angled motherboard tray and the back side cable management bar seal the and exclude any cable mess Fortulezza is designed to be a feast for the senses tower x game link. Featuring cheerful music, mesmerizing sound effects, and stunning graphics, the game transports you into a world bursting with color and charm. Each level brings a fresh visual treat, making your puzzle-solving experience even more enjoyable towerx.
    https://jii.li/Tyiun
    Its defining feature? The ability for players to set their own challenge by selecting the number of mines. This offers a dynamic gameplay experience that caters to both the cautious and the daring. And while the 95% RTP might raise eyebrows for some, the game’s unique mechanics and engaging nature make it a worthy contender in the crash game universe. For those still on the fence, the option to play the Mines game for free on SiGMA Play provides a no-strings-attached way to enjoy and try it out. The Mines game introduces a risk-free demo mode, perfect for those looking to get a feel for the game without spending real money. Mines demo provides an opportunity for players to explore the game’s intricacies, understand its unique mechanics, and strategize their approach. This game does not feature Wilds or Free Spins features. For players who are used to conventional slot games, this may be a disappointment. However, this structure suits online crash games such as Mines.

  21. Richardfek表示:

    пин ап вход пинап казино пин ап казино официальный сайт

  22. Elmerbuils表示:

    пин ап вход: пинап казино – пин ап казино официальный сайт

  23. KennethKex表示:

    pinup az: pinup az – pin up

  24. Richardfek表示:

    pin up casino pinup az pin up azerbaycan

  25. vuhndfmsw表示:

    To update Minecraft APK, you need to check the official Minecraft website or Google Play store to find the latest version. Then, download and install the new version by doing the same as when you first installed it. Before diving into the details, it’s important to note that the Mines Bet hack comes with considerable risks, both legal and in-game. If you’re curious about how this Mines hack works, read on to find out more. The premise in Craftsman is quite simple. At the start of the game, you are stranded on a deserted island armed only with your bare hands. The game has voxel aesthetics, so everything takes a block-like form: trees, stones, clouds, flowers, etc. By exploring your surroundings and collecting materials, you can assemble more and more advanced tools that will unlock more and more complex constructions. As a result, in Craftsman, you can build a world more aligned with your desires and needs.
    https://lehugubo1985.bearsfanteamshop.com/https-in-mineislandgame-com
    2. Open GameLoop and search for “Lucky Jet Hack – Signal” , find Lucky Jet Hack – Signal in the search results and click “Install” Rs. 50.00 Rs. 70.00 28.57% Lucky jet is a popular 1win casino game in which the player controls a character on a jetpack. the essence of the game is the accuracy of the odds prediction. the player has to manage to withdraw the bet at the maximum peak before the joe explodes. This software is officially approved by the Japan Curling Association and is a full-fledged game in which the movement of the stones is realistically reproduced in detail. Title: Neurocomputational analyses of electrophysiological recordings using silk-based soft electrodes in noise-induced hearing loss ᐉ Hacks, hacks, cheats and Lucky Jet signals, winning strategies, tactics and predictions with guaranteed winnings are all fraudulent scams. Crash game Lucky Jet has a built-in honesty check system, its algorithms are protected from hacking and the entire software part is located on the provider’s servers, which excludes illegal manipulation of the slot code.

  26. Richardfek表示:

    пин ап казино пин ап вход пин ап казино официальный сайт

  27. KennethKex表示:

    pin up azerbaycan: pin up – pin up

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

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