大專案中網頁多語系的維護方式
也許在許多專案上我們所說的Multi Language僅有繁體中文、簡體中文和英文,但在比較大型且跨國的專案上,可能會涉及更多的語言,例如:日文、俄文、德文、法文、西班牙文…等等,通常這些軟體內容的翻譯,需要經過更專業的單位來進行,也許這個單位不僅僅需要具備有這些國家的語言能力,也要具備有相當的軟體知識,才能配合當地民情翻譯出正確的文字,這部分往往需要專業的翻譯單位來進行。
而專業的翻譯單位通常並不具有軟體的製作能力,所以在軟體上要如何快速地進行協同作業,就變成一項非常重要的工作,而在許多專案上,我們會讓翻譯單位透過Excel來提供各國語系的翻譯文字,我們則透過軟體進行轉換,將其轉換至軟體能快速讀取的格式,講白一點就是將Excel檔案轉換成XML格式,並提供其他軟體進行讀取。
整體流程會如上圖所示,在拿到一個翻譯社提供所有語系的Excel資料後,我們會進行轉換,將文字轉換成多個XML檔案,並打包成一個ZIP檔讓轉換者進行下載。
今天我們主要來分享上圖藍色部分的處理程序,也就是那一隻轉換程式的結構和做法,我們將轉換程式設計成網頁版本,藉此提升易用性,而轉換程式操作Flow大致如下:
- 使用者上傳檔案(限制僅能上傳Excel檔案)
- 給出ZIP下載連結(提供使用者下載所有語系的XML檔)
操作上非常簡單,僅有上述這兩個步驟,而程式設計上採用JSP架構其運作邏輯如下:
- 檢查上傳檔案的格式、容量及相關資訊
- 擷取Excel中第一張工作表(也可以依照工作表名稱擷取)
- 將Excel中第一列視為語系標題,並當作存檔名稱(例如:English.xml)
- 將剩下來的每一列轉換為該檔案的語系資料,並建立XML檔案
- 將所有建立好的XML檔案進行打包(ZIP)
- 更新頁面產生ZIP檔下載路徑
在該轉換程式中,另外有利用到下述的JAVA Library:
- Apache POI – 處理與解析Excel檔案
- DOM4J – 建立XML檔案
- 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>
https://fastpillsformen.com/# Sildenafil Citrate Tablets 100mg
частная скорая наркологическая помощь eisberg.forum24.ru/?1-1-0-00002023-000-0-0-1730876367 .
buy viagra here: FastPillsForMen.com – over the counter sildenafil
неотложная наркологическая помощь неотложная наркологическая помощь .
How Asbestos Cancer Law Lawyer Mesothelioma Settlement Has
Changed My Life The Better Mesothelioma attorneys
Как купить аттестат 11 класса с официальным упрощенным обучением в Москве
generic sildenafil: Fast Pills For Men – sildenafil over the counter
наркологическая скорая в москве [url=www.taksafonchik.borda.ru/?1-2-0-00001312-000-0-0-1730876668]наркологическая скорая в москве[/url] .
наркологическая срочная помощь https://belbeer.borda.ru/?1-6-0-00000783-000-0-0-1730876081/ .
вызвать наркологическую помощь http://www.sportandpolitics.ukrbb.net/viewtopic.php?f=24&t=17890 .
неотложная наркологическая помощь [url=http://www.ivanovo.forum24.ru/?1-15-0-00000582-000-0-0-1730876715]неотложная наркологическая помощь[/url] .
наркологическая скорая в москве наркологическая скорая в москве .
наркологическая скорая помощь москва наркологическая скорая помощь москва .
Диплом пту купить официально с упрощенным обучением в Москве
Although Birchbox, one of the first beauty boxes, was the industry leader through 2015, quarterly sales at Birchbox have been surpassed by FabFitFun, Ipsy, and BoxyCharm. Since 2018, FabFitFun has had the highest quarterly sales among this competitive set. Sales at FabFitFun have roughly tripled since Q1 2018 and peaked in Q2 2020, during the early months of the COVID-19 pandemic. According to Bloomberg, BFA Industries could go public in the next 18 months. Additionally, the skin-care category continues to grow and rebound faster than its makeup counterpart, and body care is leading that sales recovery. According to NPD Group’s latest data, body product sales, including cleansers, creams lotions and exfoliators, increased by 17% in the third quarter.
http://suhinfo.ru/index.php/Bobbi_brown_eyebrow
These are not your grandma’s makeup brushes. The Detailers are a super squad of 5 vegan & cruelty-free multi-purpose tools, each hand-crafted to mimic real hair. Inspired by jade rollers, 3 of these beauties have a rolling tip on the opposite end for some skincare love. SAY WHAAAAT?! This unique brush will naturally guide your hand upward, leaving skin looking instantly polished and refined. We recommend a clear brush cleaner (colored ones may dye the bristles) or clear soap and water. Dry your brushes quickly, do not bend them, and do not put them away when they are damp. Sponges are good for pressing a product in after you have deposited it on the skin with a brush. This is because using a sponge to pick up a product and apply it on to the skin leads to a lot of wastage, because sponges soak up the product and deposit minimal amounts.
Как получить диплом стоматолога быстро и официально
частная скорая наркологическая помощь частная скорая наркологическая помощь .
вызов нарколога на дом частная скорая помощь вызов нарколога на дом частная скорая помощь .
наркологическая скорая в москве наркологическая скорая в москве .
generic sildenafil: Fast Pills For Men – order viagra
Sildenafil 100mg price cheap viagra Cheap Viagra 100mg
https://maxpillsformen.com/# Cialis without a doctor prescription
Как приобрести аттестат о среднем образовании в Москве и других городах
The ship’s Air Operations Department communicates with the ready room via the ASDO when reporting launch and touchdown occasions, and standard telephones are used for other communications.
наркологическая скорая [url=https://domsadremont.ukrbb.net/viewtopic.php?f=3&t=916/]наркологическая скорая[/url] .
What New Glenn will do
In some ways, New Glenn has already made its mark on the launch industry. Blue Origin has for years pitched the rocket to compete with both SpaceX and United Launch Alliance — a joint venture of Boeing and Lockheed Martin that buys engines from Blue Origin — for lucrative military launch contracts.
[url=https://omgprice10.com]омг[/url]
The US Space Force selected Blue Origin, ULA and SpaceX in June to compete for $5.6 billion worth of Pentagon contracts for national security missions slated to launch over the next four years.
Blue Origin also has deals with several commercial companies to launch satellites. The contracts include plans to help deploy Amazon’s Kuiper internet satellites and a recently inked deal with AST SpaceMobile to help launch the Midland, Texas-based company’s space-based cellular broadband network.
New Glenn could also be instrumental in building Blue Origin’s planned space station, called Orbital Reef. Blue Origin and it commercial partners, including Sierra Space and Boeing, among others, hope the station will one day provide a new destination for astronauts as the International Space Station is phased out of service.
https://omgprice10.com
omg ссылка
New Glenn vs. other powerful rockets
New Glenn packs significant power. Dubbed a “heavy-lift” vehicle, its capabilities lie between SpaceX’s Falcon 9 rocket and the more powerful Falcon Heavy launch vehicle.
SpaceX’s workhorse Falcon 9, for example, can haul up to 22.8 metric tons (50,265 pounds) to space. While New Glenn is capable of carrying about double that mass, it may also be roughly the same price as a Falcon 9: reportedly around $60 million to $70 million per launch.
“I think in order to compete with Falcon 9, you have to go head-to-head or better on price,” said Caleb Henry, the director of research at Quilty Space, which provides data and analysis about the space sector.
The question, however, is whether Blue Origin will be able to sustain a competitive price point, Henry added.
Still, one feature that makes New Glenn stand out is its large payload fairing, or nose cone. The component protects the cargo bay and is a whopping 23 feet (7 meters) wide — nearly 6 feet (2 meters) larger than that of SpaceX’s Falcon 9 or Falcon Heavy.
Henry said Blue Origin likely opted to outfit New Glenn with such a large fairing in order to help fulfill Bezos’ vision of the future.
What’s on board this flight
Blue Origin had planned to launch a pair of Mars-bound satellites on behalf of NASA for the first flight of New Glenn.
But delays with the rocket’s development prompted the space agency to change course, moving that flight to this spring at the earliest. So for this inaugural flight, Blue Origin opted to instead fly a “demonstrator” that will test technology needed for the company’s proposed Blue Ring spacecraft — which will aim to serve as a sort of in-space rideshare vehicle, dragging satellites deeper into space when needed.
[url=https://omgprice10.com]omg вход[/url]
The demonstrator on this New Glenn flight will remain aboard the rocket for the entire six-hour flight, Blue Origin said, and it will validate “communications capabilities from orbit to ground” as well as “test its in-space telemetry, tracking and command hardware, and ground-based radiometric tracking.”
The Blue Ring Pathfinder demonstrator is part of a deal Blue Origin inked with the US Department of Defense’s Defense Innovation Unit.
https://omgprice10.com
омг
Why Blue Origin wants to reuse rockets
Similar to SpaceX, Blue Origin is aiming to recover and refly its first-stage rocket boosters in a bid to make launches less expensive.
“Reusability is integral to radically reducing cost-per-launch,” the company said in a recent news release, using the same oft-repeated sentiment that SpaceX has touted since it began landing rocket boosters in 2015.
Bezos, however, has acknowledged the importance of reusing rocket parts since he founded the company in 2000 — two years before Musk established SpaceX. And the company has already developed its suborbital New Shepard tourism rocket to be reusable.
“It’s not a copy cat game,” Henry said. “Blue Origin has been pursuing reusable vehicles since before reusable vehicles were cool. Now it’s much more of a mainstream idea (because of SpaceX). The difference is that it’s taken Blue Origin so much longer to get to orbit.”
If successful, returning the New Glenn rocket booster for a safe landing will be a stunning feat. After expending most of its fuel to propel the rocket’s upper stage to space, the first-stage booster will need to make a clean separation. The booster must then maneuver with pinpoint guidance and reignite its engines with precision timing to avoid crashing into the ocean or the Jacklyn recovery platform.
It opens up with notes of pineapple, bergamot, and blackcurrant leaves; middle notes include pink berries, birch, and patchouli while base notes are ambergris, oakmoss, musk, and vanilla. A fruity and musky scent, this cologne defines modern masculinity – rock this cologne with confidence. It’s great on occasion, but not something that I want to wear on a daily basis, even in winter. Which, is when it is at its best. Still, a cologne that I’m glad I picked up when I could. One of the best ways to understand seasonality and how notes work with certain moods and occasions is Michael Edwards’ Fragrance Wheel, which also happens to be one of the best fragrance discovery scents on the web. Complimentary Delivery The Best in Men’s Luxury. The best smelling colognes aren’t just for your nose; they’re designed to make you feel good too. In fact, colognes can transform you into whoever you want to be. Confident businessman? Rugged outdoorsy guy who loves his dog? It doesn’t matter. The right cologne will fit whatever vibe you’re in the mood for—even if that vibe is just being a really good smelling version of yourself.
https://nova-wiki.win/index.php?title=Best_day_cream_for_dry_skin
Are you longing for fuller, more voluminous lashes? Look no further. In this expert review, we will delve into the world of eyelash serums for fuller lashes. With the advancement of cosmetic science, a range of products have emerged that claim to enhance lash growth and promote thicker, longer eyelashes. We have evaluated the top-rated lash growth serums available and explored their benefits, effectiveness, and natural ingredients. Read on to find the perfect solution for your lash goals. Looking for a reliable eyelash serum that helps your lashes grow quickly and naturally? Halooo semuanyaaa… Bentar lagi 2023 berakhir, jadi aku lagi review best serum untuk cerahkan, pudarkan dark spot, jerawat, eksfoliasi, dan lainnya. Buat best serum untuk serang jerawat dan tenangkan jerawat memang ada kandungan buat eksfoliasinya juga yaah, cuma emang aku pisahin lagi khusus bu
Как правильно купить диплом колледжа и пту в России, подводные камни
http://fastpillseasy.com/# erectile dysfunction online prescription