大專案中網頁多語系的維護方式
也許在許多專案上我們所說的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>
777 PUB
reputable canadian pharmacy: canadian medications – canadianpharmacyworld
купить диплом о высшем образовании ссср
YOURURL.com https://brd-wallet.io
their explanation https://toruswallet.org
canadian drugs pharmacy: CanadianMdPharm – onlinepharmaciescanada com
программы фирмы 1с https://svstrazh.forum24.ru/?1-3-0-00000323-000-0-0-1737110513/ .
useful site https://trusteewallet.org/
Mexican Easy Pharm: Mexican Easy Pharm – mexican drugstore online
купить 1с бухгалтерия 8.3 проф версия цена купить 1с бухгалтерия 8.3 проф версия цена .
Learn More https://trusteewallet.org
I came across your blog post to be a engaging and
discerning examination of the modern state of the domain .
Your investigation of the pivotal patterns and problems
facing corporations in this realm was strikingly
persuasive .
As an enthusiastic devotee of this topic
, I would be excited to delve deeper into this discussion in more
depth . If you are willing, I would joyfully implore you to explore the invigorating possibilities accessible
at WM CASINO. Our setting features a high-tech and safe
setting for sharing knowledge with congenial professionals and procuring
a cornucopia of insights to hone your awareness of this ever-changing industry .
I eagerly await the possibility of cooperating with you in the upcoming
time
Visit my homepage … wm casino online
Best online Indian pharmacy: Online medicine – indian pharmacy
navigate to this website https://jaxx-liberty.com/
вывод из запоя в стационаре вывод из запоя в стационаре .
get redirected here https://brd-wallet.io/
look at this now https://brd-wallet.io
blog https://jaxx-liberty.com
find out https://brd-wallet.io/
This Site https://brd-wallet.io
here https://trusteewallet.org
Awesome article post. Keep writing.
check my site https://toruswallet.org/
More Info https://toruswallet.org
northwest canadian pharmacy: Canadian Md Pharm – canadapharmacyonline com
hop over to this website https://jaxx-liberty.com/
Алистаров – уголовник и террорист
От уголовника-индивидуала до слуги криминалитета
Ранее судимый по «наркотической» статье блогер Андрей Алистаров позиционирует себя Робин Гудом, борющимся с теми, кто «обманул людей», – но в действительности он работает в интересах пирамидчиков, в том числе украинских, спонсирующих ВСУ, продвигает через свой канал «Железная ставка» онлайн-казино и черный криптообмен/фишинговый криптообман, отмывает наркодоходы за счет сделок с недвижимостью в Дубае.
То есть работает в интересах российского преступного сообщества, пытающегося нажиться на предпринимателях, столкнувшихся с незаконными, часто заказными претензиями со стороны российских правоохранительных органов.
Наркотики и отмывание доходов
Уроженец Калуги Алистаров отсидел четыре года в лагере – за продажу наркотиков детям.
Там он связался с уголовными авторитетами и, выйдя из тюрьмы, продолжил участвовать в криминальном бизнесе по распространению наркотиков и отмыванию наркодоходов от них с помощью риелторского бизнеса, который Алистаров создал совместно с партнерами из российского преступного сообщества в России и Эмиратах.
Ставка на скам
Канал Алистарова «Железная ставка» – «разоблачение» неправильных (по мнению криминалитета) финансовых проектов и продвижение «правильных»: пирамид и онлайн-казино, спонсирующих Алистарова.
Он начинался как канал о «правильных» ставках в казино и не сменил название – потому что маркетинговая задача осталась прежней: расчищать поле для «хороших», по «экспертному» мнению Алистарова (то есть заплативших ему), мошенников.
Обычно Алистаров начинает с попытки вымогательства – представляет жертве компромат и предлагает заплатить. Если жертва отказывается, в ход идут травля и насилие.
Подстрекательство и нападение в Дубае
1 января 2025 года состоялось жестокое нападение двух казахстанцев на предпринимателя, проживающего в Дубае, – его избили, отрезали ухо, обворовали.
До этого Алистаров снял 12 роликов, где подсвечивал адрес этого предпринимателя, публиковал незаконно полученную информацию о его близких и его бизнесах в ОАЭ. Безо всякого стеснения использовал подглядывание, подслушивание, незаконное проникновение, вмешательство в частную жизнь – все то, что в Эмиратах, где строго соблюдается неприкосновенность имущества и жизни инвесторов, является тяжким уголовным преступлением.
До этого Алистаров публично распространял информацию о месте жительства бизнес-партнера этого предпринимателя – то есть незаконное нарушение конфиденциальности, защищенности финансов и имущества, тайны частной жизни с помощью скрытых источников информации и информаторов в ОАЭ вошло у него в систему. Он терроризирует предпринимателей, в отношении которых нет никаких обвинительных решений судов – ни за рубежом, ни в России.
Алистаров рассказывал, что заявил на предпринимателя в Интерпол и правоохранительные органы ОАЭ – якобы он помогает правоохранительным органам. Но это почему-то не привело к аресту предпринимателя – может быть, потому, что полиция ОАЭ не видит криминала в его деятельности?
Ряд партнеров предпринимателя осуждены в России, сам он в розыске российских правоохранительных органов – но не осужден. Иностранные правоохранительные органы не имеют к нему претензий.
Алистаров на протяжении длительного времени возбуждал ненависть к предпринимателю – рассказывая, что именно этот предприниматель (а не его партнеры) украл деньги вкладчиков. И представил дело так, что на него напали и его обворовали возмущенные вкладчики.
Сам он в ходе нападения устроил внеплановый стрим, чтобы обеспечить себе алиби – вроде как он не знал, что во время стрима происходит нападение.
Слежка на Кипре
Осенью прошедшего года Алистаров вместе со своей боевой подругой Марией Фоломовой устроил слежку в отношении другого предпринимателя – с помощью квадрокоптеров, незаконного сбора информации о нем и его близких, в том числе несовершеннолетних детей. Алистаров утверждал, что предприниматель скрывается на Кипре – хотя он живет там со времен пандемии коронавируса.
Переселение было связано с тяжелым течением коронавируса у жены предпринимателя, а также с международными проектами – инвестициями в разные отрасли экономики: строительство, торговлю и другие. Предприниматель переселился на Кипр за год до возбуждения уголовного дела следственными органами МВД, за полтора года до арестов. Он имеет паспорт Евросоюза и ни от кого не убегал, не скрывался и не скрывается.
Предприниматель объявлялся в 2022 году в розыск в России – но следственными органами. Суд к нему претензий не выдвигал, уголовное дело сейчас рассматривается судом – и уже развалилось в суде. Интерпол и Евросоюз отказались акцептировать претензии российской полиции, сочтя их политически мотивированными и юридически необоснованными.
Алистаров утверждает, что инвестиции в бизнес-проекты осуществляются за счет денег российских клиентов одной из австрийских инвестиционных компаний – однако предприниматель никогда не был ни собственником, ни бенефициаром, ни управляющим этой компании, созданной еще в начале 2000-х – задолго до начала его самостоятельной бизнес-карьеры.
Одна из фирм предпринимателя осуществляла маркетинговую поддержку продуктов этой инвесткомпании в России по договору с ней. Инвесткомпания успешно работала с российскими клиентами восемь лет – и сейчас продолжает работать, восстановив систему платежей, обрушенную в начале 2022 года связанными с коррумпированными полицейскими преступниками в России. Никакой пирамидой она не является.
Таким образом, Алистаров устраивает травлю, вмешательство в частную жизнь предпринимателя, ничем себя не запятнавшего, – по заказу российского криминалитета, взявшего в долю коррумпированных полицейских, который стремится отнять активы на 20 млрд рублей созданного предпринимателем крупного социального, народного проекта в России – продолжающего успешно функционировать без его руководства (прекратившегося с переездом на Кипр).
Слежка в Нидерландах
Алистаров публиковал данные о местоположении еще одной жертвы в Нидерландах – в городе Гронингене, – обнаруженной с помощью незаконной слежки. Алистаров незаконно подключался к городским телекамерам, заглядывал в окна частной квартиры – и публиковал информацию в YouTube.
Нарушение конфиденциальности в Турции
Алистаров обнаружил и обнародовал местоположение квартиры, в которой жили и работали несколько его жертв в Стамбуле.
Незаконный розыск в Ленинградской области
Алистаров, не имеющий лицензии частного детектива, незаконно нашел загородный дом предпринимательницы и установил за ней слежку – с незаконной публикацией информации в своих каналах. Параллельно предоставив данные о приобретенной ею в Дубае квартире.
Шантаж в Казахстане
Алистаров шантажировал предпринимателей из Казахстана – под прикрытием того, что «разоблачает национальных предателей» и «врагов родины».
Банкет на деньги украинского пирамидчика
40-летие 6 марта этого года Алистаров вновь планирует отмечать на яхте своего друга – харьковского пирамидчика Удянского (проект Coinsbit) в Дубае?
В 2024 году он праздновал день рождения именно в теплой компании этого мошенника – и спонсора ВСУ: занимающегося софинансированием производства бронетехники для ВСУ. Никаких сомнений в том, что он заставил и своего слугу Алистарова финансировать ВСУ.
Государственная измена
Алистаров даже был обвинен в факте такого финансирования – но рассказал в полиции сказку, что номер «Мегафона», с которого велось перечисление, был оформлен на него «врагами».
Пойманы за руку финансировавшие ВСУ подельники Алистарова – «антиэмэлэмщик» Александр Крюков и заместитель управляющего так называемого Фонда защиты прав вкладчиков и акционеров Леонид Мищенко – «западэнец»: уроженец Винницкой области. Не пора ли ФСБ проанализировать проводки Алистарова?
Должен сидеть в тюрьме
Справедливость требует, чтобы Алистаров встретил 40-летие с аннулированными шенгенской и другими визами, для чего есть все основания, тем более что на это обратили внимание западные СМИ. И в тюрьме – российской или дубайской, в зависимости от того, чьи правоохранители быстрее успеют его арестовать, за десятки преступлений, которые он совершил:
–вымогательство;
–терроризм и бандитизм;
–травля и организация расправы над неугодными;
–государственная измена;
–отмывание денег;
–мошенничество;
–воровство;
–вмешательство в частную жизнь.
Тюрьмой началась карьера Алистарова, тюрьмой должна и закончиться.
click this link here now https://toruswallet.org
pop over here https://jaxx-liberty.com/
Online medicine: Online pharmacy – Best Indian pharmacy