Jelajahi Sumber

Merge pull request #543 from ma2tani/fess_dict

add Protwords dictionary
Shinsuke Sugaya 9 tahun lalu
induk
melakukan
58984045c4
47 mengubah file dengan 4071 tambahan dan 2 penghapusan
  1. 123 0
      src/main/java/org/codelibs/fess/app/pager/CharMappingPager.java
  2. 123 0
      src/main/java/org/codelibs/fess/app/pager/ProtwordsPager.java
  3. 75 0
      src/main/java/org/codelibs/fess/app/service/CharMappingService.java
  4. 76 0
      src/main/java/org/codelibs/fess/app/service/ProtwordsService.java
  5. 434 0
      src/main/java/org/codelibs/fess/app/web/admin/dict/mapping/AdminDictMappingAction.java
  6. 46 0
      src/main/java/org/codelibs/fess/app/web/admin/dict/mapping/CreateForm.java
  7. 23 0
      src/main/java/org/codelibs/fess/app/web/admin/dict/mapping/DownloadForm.java
  8. 33 0
      src/main/java/org/codelibs/fess/app/web/admin/dict/mapping/EditForm.java
  9. 27 0
      src/main/java/org/codelibs/fess/app/web/admin/dict/mapping/SearchForm.java
  10. 33 0
      src/main/java/org/codelibs/fess/app/web/admin/dict/mapping/UploadForm.java
  11. 381 0
      src/main/java/org/codelibs/fess/app/web/admin/dict/protwords/AdminDictProtwordsAction.java
  12. 42 0
      src/main/java/org/codelibs/fess/app/web/admin/dict/protwords/CreateForm.java
  13. 23 0
      src/main/java/org/codelibs/fess/app/web/admin/dict/protwords/DownloadForm.java
  14. 33 0
      src/main/java/org/codelibs/fess/app/web/admin/dict/protwords/EditForm.java
  15. 27 0
      src/main/java/org/codelibs/fess/app/web/admin/dict/protwords/SearchForm.java
  16. 32 0
      src/main/java/org/codelibs/fess/app/web/admin/dict/protwords/UploadForm.java
  17. 3 1
      src/main/java/org/codelibs/fess/dict/DictionaryManager.java
  18. 38 0
      src/main/java/org/codelibs/fess/dict/mapping/CharMappingCreator.java
  19. 295 0
      src/main/java/org/codelibs/fess/dict/mapping/CharMappingFile.java
  20. 140 0
      src/main/java/org/codelibs/fess/dict/mapping/CharMappingItem.java
  21. 39 0
      src/main/java/org/codelibs/fess/dict/protwords/ProtwordsCreator.java
  22. 292 0
      src/main/java/org/codelibs/fess/dict/protwords/ProtwordsFile.java
  23. 103 0
      src/main/java/org/codelibs/fess/dict/protwords/ProtwordsItem.java
  24. 31 0
      src/main/java/org/codelibs/fess/mylasta/action/FessHtmlPath.java
  25. 84 0
      src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java
  26. 85 0
      src/main/java/org/codelibs/fess/mylasta/action/FessMessages.java
  27. 54 0
      src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java
  28. 3 0
      src/main/resources/fess_config.properties
  29. 13 1
      src/main/resources/fess_dict.xml
  30. 28 0
      src/main/resources/fess_label.properties
  31. 28 0
      src/main/resources/fess_label_en.properties
  32. 30 0
      src/main/resources/fess_label_ja.properties
  33. 28 0
      src/main/resources/fess_label_ko.properties
  34. 5 0
      src/main/resources/fess_message.properties
  35. 5 0
      src/main/resources/fess_message_en.properties
  36. 5 0
      src/main/resources/fess_message_ja.properties
  37. 2 0
      src/main/resources/fess_message_ko.properties
  38. 156 0
      src/main/webapp/WEB-INF/view/admin/dict/mapping/admin_dict_mapping.jsp
  39. 127 0
      src/main/webapp/WEB-INF/view/admin/dict/mapping/admin_dict_mapping_details.jsp
  40. 103 0
      src/main/webapp/WEB-INF/view/admin/dict/mapping/admin_dict_mapping_download.jsp
  41. 129 0
      src/main/webapp/WEB-INF/view/admin/dict/mapping/admin_dict_mapping_edit.jsp
  42. 107 0
      src/main/webapp/WEB-INF/view/admin/dict/mapping/admin_dict_mapping_upload.jsp
  43. 154 0
      src/main/webapp/WEB-INF/view/admin/dict/protwords/admin_dict_protwords.jsp
  44. 123 0
      src/main/webapp/WEB-INF/view/admin/dict/protwords/admin_dict_protwords_details.jsp
  45. 103 0
      src/main/webapp/WEB-INF/view/admin/dict/protwords/admin_dict_protwords_download.jsp
  46. 120 0
      src/main/webapp/WEB-INF/view/admin/dict/protwords/admin_dict_protwords_edit.jsp
  47. 107 0
      src/main/webapp/WEB-INF/view/admin/dict/protwords/admin_dict_protwords_upload.jsp

+ 123 - 0
src/main/java/org/codelibs/fess/app/pager/CharMappingPager.java

@@ -0,0 +1,123 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.pager;
+
+import java.io.Serializable;
+import java.util.List;
+
+import org.codelibs.fess.Constants;
+
+public class CharMappingPager implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private int allRecordCount;
+
+    private int allPageCount;
+
+    private boolean existPrePage;
+
+    private boolean existNextPage;
+
+    private List<Integer> pageNumberList;
+
+    private int pageSize;
+
+    private int currentPageNumber;
+
+    public String id;
+
+    public void clear() {
+        allRecordCount = 0;
+        allPageCount = 0;
+        existPrePage = false;
+        existNextPage = false;
+        pageSize = getDefaultPageSize();
+        currentPageNumber = getDefaultCurrentPageNumber();
+
+        id = null;
+    }
+
+    protected int getDefaultPageSize() {
+        return Constants.DEFAULT_ADMIN_PAGE_SIZE;
+    }
+
+    protected int getDefaultCurrentPageNumber() {
+        return 1;
+    }
+
+    public int getAllRecordCount() {
+        return allRecordCount;
+    }
+
+    public void setAllRecordCount(final int allRecordCount) {
+        this.allRecordCount = allRecordCount;
+    }
+
+    public int getAllPageCount() {
+        return allPageCount;
+    }
+
+    public void setAllPageCount(final int allPageCount) {
+        this.allPageCount = allPageCount;
+    }
+
+    public boolean isExistPrePage() {
+        return existPrePage;
+    }
+
+    public void setExistPrePage(final boolean existPrePage) {
+        this.existPrePage = existPrePage;
+    }
+
+    public boolean isExistNextPage() {
+        return existNextPage;
+    }
+
+    public void setExistNextPage(final boolean existNextPage) {
+        this.existNextPage = existNextPage;
+    }
+
+    public int getPageSize() {
+        if (pageSize <= 0) {
+            pageSize = getDefaultPageSize();
+        }
+        return pageSize;
+    }
+
+    public void setPageSize(final int pageSize) {
+        this.pageSize = pageSize;
+    }
+
+    public int getCurrentPageNumber() {
+        if (currentPageNumber <= 0) {
+            currentPageNumber = getDefaultCurrentPageNumber();
+        }
+        return currentPageNumber;
+    }
+
+    public void setCurrentPageNumber(final int currentPageNumber) {
+        this.currentPageNumber = currentPageNumber;
+    }
+
+    public List<Integer> getPageNumberList() {
+        return pageNumberList;
+    }
+
+    public void setPageNumberList(final List<Integer> pageNumberList) {
+        this.pageNumberList = pageNumberList;
+    }
+}

+ 123 - 0
src/main/java/org/codelibs/fess/app/pager/ProtwordsPager.java

@@ -0,0 +1,123 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.pager;
+
+import java.io.Serializable;
+import java.util.List;
+
+import org.codelibs.fess.Constants;
+
+public class ProtwordsPager implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private int allRecordCount;
+
+    private int allPageCount;
+
+    private boolean existPrePage;
+
+    private boolean existNextPage;
+
+    private List<Integer> pageNumberList;
+
+    private int pageSize;
+
+    private int currentPageNumber;
+
+    public String id;
+
+    public void clear() {
+        allRecordCount = 0;
+        allPageCount = 0;
+        existPrePage = false;
+        existNextPage = false;
+        pageSize = getDefaultPageSize();
+        currentPageNumber = getDefaultCurrentPageNumber();
+
+        id = null;
+    }
+
+    protected int getDefaultPageSize() {
+        return Constants.DEFAULT_ADMIN_PAGE_SIZE;
+    }
+
+    protected int getDefaultCurrentPageNumber() {
+        return 1;
+    }
+
+    public int getAllRecordCount() {
+        return allRecordCount;
+    }
+
+    public void setAllRecordCount(final int allRecordCount) {
+        this.allRecordCount = allRecordCount;
+    }
+
+    public int getAllPageCount() {
+        return allPageCount;
+    }
+
+    public void setAllPageCount(final int allPageCount) {
+        this.allPageCount = allPageCount;
+    }
+
+    public boolean isExistPrePage() {
+        return existPrePage;
+    }
+
+    public void setExistPrePage(final boolean existPrePage) {
+        this.existPrePage = existPrePage;
+    }
+
+    public boolean isExistNextPage() {
+        return existNextPage;
+    }
+
+    public void setExistNextPage(final boolean existNextPage) {
+        this.existNextPage = existNextPage;
+    }
+
+    public int getPageSize() {
+        if (pageSize <= 0) {
+            pageSize = getDefaultPageSize();
+        }
+        return pageSize;
+    }
+
+    public void setPageSize(final int pageSize) {
+        this.pageSize = pageSize;
+    }
+
+    public int getCurrentPageNumber() {
+        if (currentPageNumber <= 0) {
+            currentPageNumber = getDefaultCurrentPageNumber();
+        }
+        return currentPageNumber;
+    }
+
+    public void setCurrentPageNumber(final int currentPageNumber) {
+        this.currentPageNumber = currentPageNumber;
+    }
+
+    public List<Integer> getPageNumberList() {
+        return pageNumberList;
+    }
+
+    public void setPageNumberList(final List<Integer> pageNumberList) {
+        this.pageNumberList = pageNumberList;
+    }
+}

+ 75 - 0
src/main/java/org/codelibs/fess/app/service/CharMappingService.java

@@ -0,0 +1,75 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.service;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.Resource;
+
+import org.codelibs.core.beans.util.BeanUtil;
+import org.codelibs.fess.Constants;
+import org.codelibs.fess.app.pager.CharMappingPager;
+import org.codelibs.fess.dict.DictionaryFile.PagingList;
+import org.codelibs.fess.dict.DictionaryManager;
+import org.codelibs.fess.dict.mapping.CharMappingFile;
+import org.codelibs.fess.dict.mapping.CharMappingItem;
+import org.dbflute.optional.OptionalEntity;
+
+public class CharMappingService {
+    @Resource
+    protected DictionaryManager dictionaryManager;
+
+    public List<CharMappingItem> getCharMappingList(final String dictId, final CharMappingPager charMappingPager) {
+        return getCharMappingFile(dictId).map(file -> {
+            final int pageSize = charMappingPager.getPageSize();
+            final PagingList<CharMappingItem> charMappingList =
+                    file.selectList((charMappingPager.getCurrentPageNumber() - 1) * pageSize, pageSize);
+
+            // update pager
+                BeanUtil.copyBeanToBean(charMappingList, charMappingPager, option -> option.include(Constants.PAGER_CONVERSION_RULE));
+                charMappingList.setPageRangeSize(5);
+                charMappingPager.setPageNumberList(charMappingList.createPageNumberList());
+
+                return (List<CharMappingItem>) charMappingList;
+            }).orElse(Collections.emptyList());
+    }
+
+    public OptionalEntity<CharMappingFile> getCharMappingFile(final String dictId) {
+        return dictionaryManager.getDictionaryFile(dictId).filter(file -> file instanceof CharMappingFile)
+                .map(file -> OptionalEntity.of((CharMappingFile) file)).orElse(OptionalEntity.empty());
+    }
+
+    public OptionalEntity<CharMappingItem> getCharMappingItem(final String dictId, final long id) {
+        return getCharMappingFile(dictId).map(file -> file.get(id).get());
+    }
+
+    public void store(final String dictId, final CharMappingItem charMappingItem) {
+        getCharMappingFile(dictId).ifPresent(file -> {
+            if (charMappingItem.getId() == 0) {
+                file.insert(charMappingItem);
+            } else {
+                file.update(charMappingItem);
+            }
+        });
+    }
+
+    public void delete(final String dictId, final CharMappingItem charMappingItem) {
+        getCharMappingFile(dictId).ifPresent(file -> {
+            file.delete(charMappingItem);
+        });
+    }
+}

+ 76 - 0
src/main/java/org/codelibs/fess/app/service/ProtwordsService.java

@@ -0,0 +1,76 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.service;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.Resource;
+
+import org.codelibs.core.beans.util.BeanUtil;
+import org.codelibs.fess.Constants;
+import org.codelibs.fess.app.pager.ProtwordsPager;
+import org.codelibs.fess.dict.DictionaryFile.PagingList;
+import org.codelibs.fess.dict.DictionaryManager;
+import org.codelibs.fess.dict.protwords.ProtwordsFile;
+import org.codelibs.fess.dict.protwords.ProtwordsItem;
+import org.dbflute.optional.OptionalEntity;
+
+public class ProtwordsService {
+    @Resource
+    protected DictionaryManager dictionaryManager;
+
+    public List<ProtwordsItem> getProtwordsList(final String dictId, final ProtwordsPager protwordsPager) {
+        return getProtwordsFile(dictId).map(
+                file -> {
+                    final int pageSize = protwordsPager.getPageSize();
+                    final PagingList<ProtwordsItem> protwordsList =
+                            file.selectList((protwordsPager.getCurrentPageNumber() - 1) * pageSize, pageSize);
+
+                    // update pager
+                    BeanUtil.copyBeanToBean(protwordsList, protwordsPager, option -> option.include(Constants.PAGER_CONVERSION_RULE));
+                    protwordsList.setPageRangeSize(5);
+                    protwordsPager.setPageNumberList(protwordsList.createPageNumberList());
+
+                    return (List<ProtwordsItem>) protwordsList;
+                }).orElse(Collections.emptyList());
+    }
+
+    public OptionalEntity<ProtwordsFile> getProtwordsFile(final String dictId) {
+        return dictionaryManager.getDictionaryFile(dictId).filter(file -> file instanceof ProtwordsFile)
+                .map(file -> OptionalEntity.of((ProtwordsFile) file)).orElse(OptionalEntity.empty());
+    }
+
+    public OptionalEntity<ProtwordsItem> getProtwordsItem(final String dictId, final long id) {
+        return getProtwordsFile(dictId).map(file -> file.get(id).get());
+    }
+
+    public void store(final String dictId, final ProtwordsItem protwordsItem) {
+        getProtwordsFile(dictId).ifPresent(file -> {
+            if (protwordsItem.getId() == 0) {
+                file.insert(protwordsItem);
+            } else {
+                file.update(protwordsItem);
+            }
+        });
+    }
+
+    public void delete(final String dictId, final ProtwordsItem protwordsItem) {
+        getProtwordsFile(dictId).ifPresent(file -> {
+            file.delete(protwordsItem);
+        });
+    }
+}

+ 434 - 0
src/main/java/org/codelibs/fess/app/web/admin/dict/mapping/AdminDictMappingAction.java

@@ -0,0 +1,434 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.admin.dict.mapping;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Resource;
+
+import org.codelibs.core.beans.util.BeanUtil;
+import org.codelibs.core.lang.StringUtil;
+import org.codelibs.fess.Constants;
+import org.codelibs.fess.app.pager.CharMappingPager;
+import org.codelibs.fess.app.service.CharMappingService;
+import org.codelibs.fess.app.web.CrudMode;
+import org.codelibs.fess.app.web.admin.dict.AdminDictAction;
+import org.codelibs.fess.app.web.base.FessAdminAction;
+import org.codelibs.fess.dict.mapping.CharMappingItem;
+import org.codelibs.fess.util.RenderDataUtil;
+import org.dbflute.optional.OptionalEntity;
+import org.dbflute.optional.OptionalThing;
+import org.lastaflute.web.Execute;
+import org.lastaflute.web.response.ActionResponse;
+import org.lastaflute.web.response.HtmlResponse;
+import org.lastaflute.web.response.render.RenderData;
+import org.lastaflute.web.ruts.process.ActionRuntime;
+import org.lastaflute.web.validation.VaErrorHook;
+
+/**
+ * @author nullpos
+ * @author ma2tani
+ */
+public class AdminDictMappingAction extends FessAdminAction {
+
+    // ===================================================================================
+    //                                                                           Attribute
+    //                                                                           =========
+    @Resource
+    private CharMappingService charMappingService;
+    @Resource
+    private CharMappingPager charMappingPager;
+
+    // ===================================================================================
+    //                                                                               Hook
+    //                                                                              ======
+    @Override
+    protected void setupHtmlData(final ActionRuntime runtime) {
+        super.setupHtmlData(runtime);
+        runtime.registerData("helpLink", systemHelper.getHelpLink(fessConfig.getOnlineHelpNameDictMapping()));
+    }
+
+    // ===================================================================================
+    //                                                                      Search Execute
+    //                                                                      ==============
+    @Execute
+    public HtmlResponse index(final SearchForm form) {
+        validate(form, messages -> {}, () -> asDictIndexHtml());
+        return asHtml(path_AdminDictMapping_AdminDictMappingJsp).renderWith(data -> {
+            searchPaging(data, form);
+        });
+    }
+
+    @Execute
+    public HtmlResponse list(final OptionalThing<Integer> pageNumber, final SearchForm form) {
+        validate(form, messages -> {}, () -> asDictIndexHtml());
+        pageNumber.ifPresent(num -> {
+            charMappingPager.setCurrentPageNumber(pageNumber.get());
+        }).orElse(() -> {
+            charMappingPager.setCurrentPageNumber(0);
+        });
+        return asHtml(path_AdminDictMapping_AdminDictMappingJsp).renderWith(data -> {
+            searchPaging(data, form);
+        });
+    }
+
+    @Execute
+    public HtmlResponse search(final SearchForm form) {
+        validate(form, messages -> {}, () -> asDictIndexHtml());
+        copyBeanToBean(form, charMappingPager, op -> op.exclude(Constants.PAGER_CONVERSION_RULE));
+        return asHtml(path_AdminDictMapping_AdminDictMappingJsp).renderWith(data -> {
+            searchPaging(data, form);
+        });
+    }
+
+    @Execute
+    public HtmlResponse reset(final SearchForm form) {
+        validate(form, messages -> {}, () -> asDictIndexHtml());
+        charMappingPager.clear();
+        return asHtml(path_AdminDictMapping_AdminDictMappingJsp).renderWith(data -> {
+            searchPaging(data, form);
+        });
+    }
+
+    protected void searchPaging(final RenderData data, final SearchForm form) {
+        // page navi
+        RenderDataUtil.register(data, "charMappingItemItems", charMappingService.getCharMappingList(form.dictId, charMappingPager));
+
+        // restore from pager
+        BeanUtil.copyBeanToBean(charMappingPager, form, op -> {
+            op.exclude(Constants.PAGER_CONVERSION_RULE);
+        });
+    }
+
+    // ===================================================================================
+    //                                                                        Edit Execute
+    //                                                                        ============
+    // -----------------------------------------------------
+    //                                            Entry Page
+    //                                            ----------
+    @Execute
+    public HtmlResponse createnew(final String dictId) {
+        saveToken();
+        return asHtml(path_AdminDictMapping_AdminDictMappingEditJsp).useForm(CreateForm.class, op -> {
+            op.setup(form -> {
+                form.initialize();
+                form.crudMode = CrudMode.CREATE;
+                form.dictId = dictId;
+            });
+        });
+    }
+
+    @Execute
+    public HtmlResponse edit(final EditForm form) {
+        validate(form, messages -> {}, () -> asListHtml(form.dictId));
+        charMappingService
+                .getCharMappingItem(form.dictId, form.id)
+                .ifPresent(entity -> {
+                    form.inputs = entity.getInputsValue();
+                    form.output = entity.getOutput();
+                })
+                .orElse(() -> {
+                    throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.getDisplayId()),
+                            () -> asListHtml(form.dictId));
+                });
+        saveToken();
+        if (form.crudMode.intValue() == CrudMode.EDIT) {
+            // back
+            form.crudMode = CrudMode.DETAILS;
+            return asDetailsHtml();
+        } else {
+            form.crudMode = CrudMode.EDIT;
+            return asEditHtml();
+        }
+    }
+
+    // -----------------------------------------------------
+    //                                               Details
+    //                                               -------
+    @Execute
+    public HtmlResponse details(final String dictId, final int crudMode, final long id) {
+        verifyCrudMode(crudMode, CrudMode.DETAILS, dictId);
+        saveToken();
+        return asDetailsHtml().useForm(
+                EditForm.class,
+                op -> {
+                    op.setup(form -> {
+                        charMappingService
+                                .getCharMappingItem(dictId, id)
+                                .ifPresent(entity -> {
+                                    form.inputs = entity.getInputsValue();
+                                    form.output = entity.getOutput();
+                                })
+                                .orElse(() -> {
+                                    throwValidationError(
+                                            messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, dictId + ":" + id),
+                                            () -> asListHtml(dictId));
+                                });
+                        form.id = id;
+                        form.crudMode = crudMode;
+                        form.dictId = dictId;
+                    });
+                });
+    }
+
+    // -----------------------------------------------------
+    //                                              Download
+    //                                               -------
+    @Execute
+    public HtmlResponse downloadpage(final String dictId) {
+        saveToken();
+        return asHtml(path_AdminDictMapping_AdminDictMappingDownloadJsp).useForm(DownloadForm.class, op -> {
+            op.setup(form -> {
+                form.dictId = dictId;
+            });
+        }).renderWith(data -> {
+            charMappingService.getCharMappingFile(dictId).ifPresent(file -> {
+                RenderDataUtil.register(data, "path", file.getPath());
+            }).orElse(() -> {
+                throwValidationError(messages -> messages.addErrorsFailedToDownloadMappingFile(GLOBAL), () -> asDictIndexHtml());
+            });
+        });
+    }
+
+    @Execute
+    public ActionResponse download(final DownloadForm form) {
+        validate(form, messages -> {}, () -> downloadpage(form.dictId));
+        verifyTokenKeep(() -> downloadpage(form.dictId));
+        return charMappingService.getCharMappingFile(form.dictId).map(file -> {
+            return asStream(new File(file.getPath()).getName()).contentTypeOctetStream().stream(out -> {
+                try (InputStream inputStream = file.getInputStream()) {
+                    out.write(inputStream);
+                }
+            });
+        }).orElseGet(() -> {
+            throwValidationError(messages -> messages.addErrorsFailedToDownloadMappingFile(GLOBAL), () -> downloadpage(form.dictId));
+            return null;
+        });
+    }
+
+    // -----------------------------------------------------
+    //                                                Upload
+    //                                               -------
+    @Execute
+    public HtmlResponse uploadpage(final String dictId) {
+        saveToken();
+        return asHtml(path_AdminDictMapping_AdminDictMappingUploadJsp).useForm(UploadForm.class, op -> {
+            op.setup(form -> {
+                form.dictId = dictId;
+            });
+        }).renderWith(data -> {
+            charMappingService.getCharMappingFile(dictId).ifPresent(file -> {
+                RenderDataUtil.register(data, "path", file.getPath());
+            }).orElse(() -> {
+                throwValidationError(messages -> messages.addErrorsFailedToDownloadMappingFile(GLOBAL), () -> asDictIndexHtml());
+            });
+        });
+    }
+
+    @Execute
+    public HtmlResponse upload(final UploadForm form) {
+        validate(form, messages -> {}, () -> uploadpage(form.dictId));
+        verifyToken(() -> uploadpage(form.dictId));
+        return charMappingService.getCharMappingFile(form.dictId).map(file -> {
+            try (InputStream inputStream = form.charMappingFile.getInputStream()) {
+                file.update(inputStream);
+            } catch (final IOException e) {
+                throwValidationError(messages -> messages.addErrorsFailedToUploadMappingFile(GLOBAL), () -> {
+                    return redirectWith(getClass(), moreUrl("uploadpage/" + form.dictId));
+                });
+            }
+            saveInfo(messages -> messages.addSuccessUploadMappingFile(GLOBAL));
+            return redirectWith(getClass(), moreUrl("uploadpage/" + form.dictId));
+        }).orElseGet(() -> {
+            throwValidationError(messages -> messages.addErrorsFailedToUploadMappingFile(GLOBAL), () -> uploadpage(form.dictId));
+            return null;
+        });
+
+    }
+
+    // -----------------------------------------------------
+    //                                         Actually Crud
+    //                                         -------------
+    @Execute
+    public HtmlResponse create(final CreateForm form) {
+        verifyCrudMode(form.crudMode, CrudMode.CREATE, form.dictId);
+        validate(form, messages -> {}, () -> asEditHtml());
+        verifyToken(() -> asEditHtml());
+        createCharMappingItem(form, () -> asEditHtml()).ifPresent(
+                entity -> {
+                    try {
+                        charMappingService.store(form.dictId, entity);
+                        saveInfo(messages -> messages.addSuccessCrudCreateCrudTable(GLOBAL));
+                    } catch (final Exception e) {
+                        throwValidationError(messages -> messages.addErrorsCrudFailedToCreateCrudTable(GLOBAL, buildThrowableMessage(e)),
+                                () -> asEditHtml());
+                    }
+                }).orElse(() -> {
+            throwValidationError(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL), () -> asEditHtml());
+        });
+        return redirectWith(getClass(), moreUrl("list/1").params("dictId", form.dictId));
+    }
+
+    @Execute
+    public HtmlResponse update(final EditForm form) {
+        verifyCrudMode(form.crudMode, CrudMode.EDIT, form.dictId);
+        validate(form, messages -> {}, () -> asEditHtml());
+        verifyToken(() -> asEditHtml());
+        createCharMappingItem(form, () -> asEditHtml()).ifPresent(
+                entity -> {
+                    try {
+                        charMappingService.store(form.dictId, entity);
+                        saveInfo(messages -> messages.addSuccessCrudUpdateCrudTable(GLOBAL));
+                    } catch (final Exception e) {
+                        throwValidationError(messages -> messages.addErrorsCrudFailedToUpdateCrudTable(GLOBAL, buildThrowableMessage(e)),
+                                () -> asEditHtml());
+                    }
+                }).orElse(() -> {
+            throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.getDisplayId()), () -> asEditHtml());
+        });
+        return redirectWith(getClass(), moreUrl("list/1").params("dictId", form.dictId));
+    }
+
+    @Execute
+    public HtmlResponse delete(final EditForm form) {
+        verifyCrudMode(form.crudMode, CrudMode.DETAILS, form.dictId);
+        verifyToken(() -> asDetailsHtml());
+        validate(form, messages -> {}, () -> asDetailsHtml());
+        charMappingService
+                .getCharMappingItem(form.dictId, form.id)
+                .ifPresent(
+                        entity -> {
+                            try {
+                                charMappingService.delete(form.dictId, entity);
+                                saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL));
+                            } catch (final Exception e) {
+                                throwValidationError(
+                                        messages -> messages.addErrorsCrudFailedToDeleteCrudTable(GLOBAL, buildThrowableMessage(e)),
+                                        () -> asEditHtml());
+                            }
+                        })
+                .orElse(() -> {
+                    throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.getDisplayId()),
+                            () -> asDetailsHtml());
+                });
+        return redirectWith(getClass(), moreUrl("list/1").params("dictId", form.dictId));
+    }
+
+    //===================================================================================
+    //                                                                        Assist Logic
+    //                                                                        ============
+
+    private OptionalEntity<CharMappingItem> getEntity(final CreateForm form) {
+        switch (form.crudMode) {
+        case CrudMode.CREATE:
+            final CharMappingItem entity = new CharMappingItem(0, StringUtil.EMPTY_STRINGS, StringUtil.EMPTY);
+            return OptionalEntity.of(entity);
+        case CrudMode.EDIT:
+            if (form instanceof EditForm) {
+                return charMappingService.getCharMappingItem(form.dictId, ((EditForm) form).id);
+            }
+            break;
+        default:
+            break;
+        }
+        return OptionalEntity.empty();
+    }
+
+    protected OptionalEntity<CharMappingItem> createCharMappingItem(final CreateForm form, final VaErrorHook hook) {
+        return getEntity(form).map(entity -> {
+            final String[] newInputs = splitLine(form.inputs);
+            validateMappingString(newInputs, "inputs", hook);
+            entity.setNewInputs(newInputs);
+            final String newOutput = form.output;
+            entity.setNewOutput(newOutput);
+            return entity;
+        });
+    }
+
+    // ===================================================================================
+    //                                                                        Small Helper
+    //                                                                        ============
+    protected void verifyCrudMode(final int crudMode, final int expectedMode, final String dictId) {
+        if (crudMode != expectedMode) {
+            throwValidationError(messages -> {
+                messages.addErrorsCrudInvalidMode(GLOBAL, String.valueOf(expectedMode), String.valueOf(crudMode));
+            }, () -> asListHtml(dictId));
+        }
+    }
+
+    private void validateMappingString(final String[] values, final String propertyName, final VaErrorHook hook) {
+        if (values.length == 0) {
+            return;
+        }
+        for (final String value : values) {
+            if (value.indexOf(',') >= 0) {
+                throwValidationError(messages -> {
+                    messages.addErrorsInvalidStrIsIncluded(propertyName, value, ",");
+                }, hook);
+            }
+            if (value.indexOf("=>") >= 0) {
+                throwValidationError(messages -> {
+                    messages.addErrorsInvalidStrIsIncluded(propertyName, value, "=>");
+                }, hook);
+            }
+        }
+    }
+
+    private String[] splitLine(final String value) {
+        if (StringUtil.isBlank(value)) {
+            return StringUtil.EMPTY_STRINGS;
+        }
+        final String[] values = value.split("[\r\n]");
+        final List<String> list = new ArrayList<>(values.length);
+        for (final String line : values) {
+            if (StringUtil.isNotBlank(line)) {
+                list.add(line.trim());
+            }
+        }
+        return list.toArray(new String[list.size()]);
+    }
+
+    // ===================================================================================
+    //                                                                              JSP
+    //                                                                           =========
+
+    protected HtmlResponse asDictIndexHtml() {
+        return redirect(AdminDictAction.class);
+    }
+
+    private HtmlResponse asListHtml(final String dictId) {
+        return asHtml(path_AdminDictMapping_AdminDictMappingJsp).renderWith(data -> {
+            RenderDataUtil.register(data, "charMappingItemItems", charMappingService.getCharMappingList(dictId, charMappingPager));
+        }).useForm(SearchForm.class, setup -> {
+            setup.setup(form -> {
+                copyBeanToBean(charMappingPager, form, op -> op.include("id"));
+            });
+        });
+    }
+
+    private HtmlResponse asEditHtml() {
+        return asHtml(path_AdminDictMapping_AdminDictMappingEditJsp);
+    }
+
+    private HtmlResponse asDetailsHtml() {
+        return asHtml(path_AdminDictMapping_AdminDictMappingDetailsJsp);
+    }
+
+}

+ 46 - 0
src/main/java/org/codelibs/fess/app/web/admin/dict/mapping/CreateForm.java

@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.admin.dict.mapping;
+
+import javax.validation.constraints.Size;
+
+import org.codelibs.fess.app.web.CrudMode;
+import org.lastaflute.web.validation.Required;
+import org.lastaflute.web.validation.theme.conversion.ValidateTypeFailure;
+
+/**
+ * @author nullpos
+ * @author ma2tani
+ */
+public class CreateForm {
+
+    @Required
+    public String dictId;
+
+    @ValidateTypeFailure
+    public Integer crudMode;
+
+    @Required
+    @Size(max = 1000)
+    public String inputs;
+
+    @Size(min = 1, max = 1000)
+    public String output;
+
+    public void initialize() {
+        crudMode = CrudMode.CREATE;
+    }
+}

+ 23 - 0
src/main/java/org/codelibs/fess/app/web/admin/dict/mapping/DownloadForm.java

@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.admin.dict.mapping;
+
+import org.lastaflute.web.validation.Required;
+
+public class DownloadForm {
+    @Required
+    public String dictId;
+}

+ 33 - 0
src/main/java/org/codelibs/fess/app/web/admin/dict/mapping/EditForm.java

@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.admin.dict.mapping;
+
+import org.lastaflute.web.validation.Required;
+import org.lastaflute.web.validation.theme.conversion.ValidateTypeFailure;
+
+/**
+ * @author nullpos
+ */
+public class EditForm extends CreateForm {
+
+    @Required
+    @ValidateTypeFailure
+    public Long id;
+
+    public String getDisplayId() {
+        return dictId + ":" + id;
+    }
+}

+ 27 - 0
src/main/java/org/codelibs/fess/app/web/admin/dict/mapping/SearchForm.java

@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.admin.dict.mapping;
+
+import org.lastaflute.web.validation.Required;
+
+/**
+ * @author nullpos
+ */
+public class SearchForm {
+
+    @Required
+    public String dictId;
+}

+ 33 - 0
src/main/java/org/codelibs/fess/app/web/admin/dict/mapping/UploadForm.java

@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.admin.dict.mapping;
+
+import org.lastaflute.web.ruts.multipart.MultipartFormFile;
+import org.lastaflute.web.validation.Required;
+
+/**
+ * @author nullpos
+ * @author ma2tani
+ */
+public class UploadForm {
+
+    @Required
+    public String dictId;
+
+    @Required
+    public MultipartFormFile charMappingFile;
+
+}

+ 381 - 0
src/main/java/org/codelibs/fess/app/web/admin/dict/protwords/AdminDictProtwordsAction.java

@@ -0,0 +1,381 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.admin.dict.protwords;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.annotation.Resource;
+
+import org.codelibs.core.beans.util.BeanUtil;
+import org.codelibs.core.lang.StringUtil;
+import org.codelibs.fess.Constants;
+import org.codelibs.fess.app.pager.ProtwordsPager;
+import org.codelibs.fess.app.service.ProtwordsService;
+import org.codelibs.fess.app.web.CrudMode;
+import org.codelibs.fess.app.web.admin.dict.AdminDictAction;
+import org.codelibs.fess.app.web.base.FessAdminAction;
+import org.codelibs.fess.dict.protwords.ProtwordsItem;
+import org.codelibs.fess.util.RenderDataUtil;
+import org.dbflute.optional.OptionalEntity;
+import org.dbflute.optional.OptionalThing;
+import org.lastaflute.web.Execute;
+import org.lastaflute.web.response.ActionResponse;
+import org.lastaflute.web.response.HtmlResponse;
+import org.lastaflute.web.response.render.RenderData;
+import org.lastaflute.web.ruts.process.ActionRuntime;
+import org.lastaflute.web.validation.VaErrorHook;
+
+/**
+ * @author ma2tani
+ */
+public class AdminDictProtwordsAction extends FessAdminAction {
+
+    // ===================================================================================
+    //                                                                           Attribute
+    //                                                                           =========
+    @Resource
+    private ProtwordsService protwordsService;
+    @Resource
+    private ProtwordsPager protwordsPager;
+
+    // ===================================================================================
+    //                                                                               Hook
+    //                                                                              ======
+    @Override
+    protected void setupHtmlData(final ActionRuntime runtime) {
+        super.setupHtmlData(runtime);
+        runtime.registerData("helpLink", systemHelper.getHelpLink(fessConfig.getOnlineHelpNameDictProtwords()));
+    }
+
+    // ===================================================================================
+    //                                                                      Search Execute
+    //                                                                      ==============
+    @Execute
+    public HtmlResponse index(final SearchForm form) {
+        validate(form, messages -> {}, () -> asDictIndexHtml());
+        return asHtml(path_AdminDictProtwords_AdminDictProtwordsJsp).renderWith(data -> {
+            searchPaging(data, form);
+        });
+    }
+
+    @Execute
+    public HtmlResponse list(final OptionalThing<Integer> pageNumber, final SearchForm form) {
+        validate(form, messages -> {}, () -> asDictIndexHtml());
+        pageNumber.ifPresent(num -> {
+            protwordsPager.setCurrentPageNumber(pageNumber.get());
+        }).orElse(() -> {
+            protwordsPager.setCurrentPageNumber(0);
+        });
+        return asHtml(path_AdminDictProtwords_AdminDictProtwordsJsp).renderWith(data -> {
+            searchPaging(data, form);
+        });
+    }
+
+    @Execute
+    public HtmlResponse search(final SearchForm form) {
+        validate(form, messages -> {}, () -> asDictIndexHtml());
+        copyBeanToBean(form, protwordsPager, op -> op.exclude(Constants.PAGER_CONVERSION_RULE));
+        return asHtml(path_AdminDictProtwords_AdminDictProtwordsJsp).renderWith(data -> {
+            searchPaging(data, form);
+        });
+    }
+
+    @Execute
+    public HtmlResponse reset(final SearchForm form) {
+        validate(form, messages -> {}, () -> asDictIndexHtml());
+        protwordsPager.clear();
+        return asHtml(path_AdminDictProtwords_AdminDictProtwordsJsp).renderWith(data -> {
+            searchPaging(data, form);
+        });
+    }
+
+    protected void searchPaging(final RenderData data, final SearchForm form) {
+        // page navi
+        RenderDataUtil.register(data, "protwordsItemItems", protwordsService.getProtwordsList(form.dictId, protwordsPager));
+
+        // restore from pager
+        BeanUtil.copyBeanToBean(protwordsPager, form, op -> {
+            op.exclude(Constants.PAGER_CONVERSION_RULE);
+        });
+    }
+
+    // ===================================================================================
+    //                                                                        Edit Execute
+    //                                                                        ============
+    // -----------------------------------------------------
+    //                                            Entry Page
+    //                                            ----------
+    @Execute
+    public HtmlResponse createnew(final String dictId) {
+        saveToken();
+        return asHtml(path_AdminDictProtwords_AdminDictProtwordsEditJsp).useForm(CreateForm.class, op -> {
+            op.setup(form -> {
+                form.initialize();
+                form.crudMode = CrudMode.CREATE;
+                form.dictId = dictId;
+            });
+        });
+    }
+
+    @Execute
+    public HtmlResponse edit(final EditForm form) {
+        validate(form, messages -> {}, () -> asListHtml(form.dictId));
+        protwordsService
+                .getProtwordsItem(form.dictId, form.id)
+                .ifPresent(entity -> {
+                    form.input = entity.getInputValue();
+                })
+                .orElse(() -> {
+                    throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.getDisplayId()),
+                            () -> asListHtml(form.dictId));
+                });
+        saveToken();
+        if (form.crudMode.intValue() == CrudMode.EDIT) {
+            // back
+            form.crudMode = CrudMode.DETAILS;
+            return asDetailsHtml();
+        } else {
+            form.crudMode = CrudMode.EDIT;
+            return asEditHtml();
+        }
+    }
+
+    // -----------------------------------------------------
+    //                                               Details
+    //                                               -------
+    @Execute
+    public HtmlResponse details(final String dictId, final int crudMode, final long id) {
+        verifyCrudMode(crudMode, CrudMode.DETAILS, dictId);
+        saveToken();
+        return asDetailsHtml().useForm(
+                EditForm.class,
+                op -> {
+                    op.setup(form -> {
+                        protwordsService
+                                .getProtwordsItem(dictId, id)
+                                .ifPresent(entity -> {
+                                    form.input = entity.getInputValue();
+                                })
+                                .orElse(() -> {
+                                    throwValidationError(
+                                            messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, dictId + ":" + id),
+                                            () -> asListHtml(dictId));
+                                });
+                        form.id = id;
+                        form.crudMode = crudMode;
+                        form.dictId = dictId;
+                    });
+                });
+    }
+
+    // -----------------------------------------------------
+    //                                              Download
+    //                                               -------
+    @Execute
+    public HtmlResponse downloadpage(final String dictId) {
+        saveToken();
+        return asHtml(path_AdminDictProtwords_AdminDictProtwordsDownloadJsp).useForm(DownloadForm.class, op -> {
+            op.setup(form -> {
+                form.dictId = dictId;
+            });
+        }).renderWith(data -> {
+            protwordsService.getProtwordsFile(dictId).ifPresent(file -> {
+                RenderDataUtil.register(data, "path", file.getPath());
+            }).orElse(() -> {
+                throwValidationError(messages -> messages.addErrorsFailedToDownloadProtwordsFile(GLOBAL), () -> asDictIndexHtml());
+            });
+        });
+    }
+
+    @Execute
+    public ActionResponse download(final DownloadForm form) {
+        validate(form, messages -> {}, () -> downloadpage(form.dictId));
+        verifyTokenKeep(() -> downloadpage(form.dictId));
+        return protwordsService.getProtwordsFile(form.dictId).map(file -> {
+            return asStream(new File(file.getPath()).getName()).contentTypeOctetStream().stream(out -> {
+                try (InputStream inputStream = file.getInputStream()) {
+                    out.write(inputStream);
+                }
+            });
+        }).orElseGet(() -> {
+            throwValidationError(messages -> messages.addErrorsFailedToDownloadProtwordsFile(GLOBAL), () -> downloadpage(form.dictId));
+            return null;
+        });
+    }
+
+    // -----------------------------------------------------
+    //                                                Upload
+    //                                               -------
+    @Execute
+    public HtmlResponse uploadpage(final String dictId) {
+        saveToken();
+        return asHtml(path_AdminDictProtwords_AdminDictProtwordsUploadJsp).useForm(UploadForm.class, op -> {
+            op.setup(form -> {
+                form.dictId = dictId;
+            });
+        }).renderWith(data -> {
+            protwordsService.getProtwordsFile(dictId).ifPresent(file -> {
+                RenderDataUtil.register(data, "path", file.getPath());
+            }).orElse(() -> {
+                throwValidationError(messages -> messages.addErrorsFailedToDownloadProtwordsFile(GLOBAL), () -> asDictIndexHtml());
+            });
+        });
+    }
+
+    @Execute
+    public HtmlResponse upload(final UploadForm form) {
+        validate(form, messages -> {}, () -> uploadpage(form.dictId));
+        verifyToken(() -> uploadpage(form.dictId));
+        return protwordsService.getProtwordsFile(form.dictId).map(file -> {
+            try (InputStream inputStream = form.protwordsFile.getInputStream()) {
+                file.update(inputStream);
+            } catch (final IOException e) {
+                throwValidationError(messages -> messages.addErrorsFailedToUploadProtwordsFile(GLOBAL), () -> {
+                    return redirectWith(getClass(), moreUrl("uploadpage/" + form.dictId));
+                });
+            }
+            saveInfo(messages -> messages.addSuccessUploadSynonymFile(GLOBAL));
+            return redirectWith(getClass(), moreUrl("uploadpage/" + form.dictId));
+        }).orElseGet(() -> {
+            throwValidationError(messages -> messages.addErrorsFailedToUploadProtwordsFile(GLOBAL), () -> uploadpage(form.dictId));
+            return null;
+        });
+
+    }
+
+    // -----------------------------------------------------
+    //                                         Actually Crud
+    //                                         -------------
+    @Execute
+    public HtmlResponse create(final CreateForm form) {
+        verifyCrudMode(form.crudMode, CrudMode.CREATE, form.dictId);
+        validate(form, messages -> {}, () -> asEditHtml());
+        verifyToken(() -> asEditHtml());
+        createProtwordsItem(form, () -> asEditHtml()).ifPresent(entity -> {
+            protwordsService.store(form.dictId, entity);
+            saveInfo(messages -> messages.addSuccessCrudCreateCrudTable(GLOBAL));
+        }).orElse(() -> throwValidationError(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL), () -> asEditHtml()));
+        return redirectWith(getClass(), moreUrl("list/1").params("dictId", form.dictId));
+    }
+
+    @Execute
+    public HtmlResponse update(final EditForm form) {
+        verifyCrudMode(form.crudMode, CrudMode.EDIT, form.dictId);
+        validate(form, messages -> {}, () -> asEditHtml());
+        verifyToken(() -> asEditHtml());
+        createProtwordsItem(form, () -> asEditHtml()).ifPresent(entity -> {
+            protwordsService.store(form.dictId, entity);
+            saveInfo(messages -> messages.addSuccessCrudUpdateCrudTable(GLOBAL));
+        }).orElse(
+                () -> throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.getDisplayId()),
+                        () -> asEditHtml()));
+        return redirectWith(getClass(), moreUrl("list/1").params("dictId", form.dictId));
+    }
+
+    @Execute
+    public HtmlResponse delete(final EditForm form) {
+        verifyCrudMode(form.crudMode, CrudMode.DETAILS, form.dictId);
+        verifyToken(() -> asDetailsHtml());
+        validate(form, messages -> {}, () -> asDetailsHtml());
+        protwordsService
+                .getProtwordsItem(form.dictId, form.id)
+                .ifPresent(entity -> {
+                    protwordsService.delete(form.dictId, entity);
+                    saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL));
+                })
+                .orElse(() -> {
+                    throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.getDisplayId()),
+                            () -> asDetailsHtml());
+                });
+        return redirectWith(getClass(), moreUrl("list/1").params("dictId", form.dictId));
+    }
+
+    //===================================================================================
+    //                                                                        Assist Logic
+    //                                                                        ============
+
+    private OptionalEntity<ProtwordsItem> getEntity(final CreateForm form) {
+        switch (form.crudMode) {
+        case CrudMode.CREATE:
+            final ProtwordsItem entity = new ProtwordsItem(0, StringUtil.EMPTY);
+            return OptionalEntity.of(entity);
+        case CrudMode.EDIT:
+            if (form instanceof EditForm) {
+                return protwordsService.getProtwordsItem(form.dictId, ((EditForm) form).id);
+            }
+            break;
+        default:
+            break;
+        }
+        return OptionalEntity.empty();
+    }
+
+    protected OptionalEntity<ProtwordsItem> createProtwordsItem(final CreateForm form, final VaErrorHook hook) {
+        return getEntity(form).map(entity -> {
+            final String newInput = form.input;
+            validateProtwordsString(newInput, "input", hook);
+            entity.setNewInput(newInput);
+            return entity;
+        });
+    }
+
+    // ===================================================================================
+    //                                                                        Small Helper
+    //                                                                        ============
+    protected void verifyCrudMode(final int crudMode, final int expectedMode, final String dictId) {
+        if (crudMode != expectedMode) {
+            throwValidationError(messages -> {
+                messages.addErrorsCrudInvalidMode(GLOBAL, String.valueOf(expectedMode), String.valueOf(crudMode));
+            }, () -> asListHtml(dictId));
+        }
+    }
+
+    private void validateProtwordsString(final String values, final String propertyName, final VaErrorHook hook) {
+        if (values.length() == 0) {
+            return;
+        }
+        // TODO validation
+    }
+
+    // ===================================================================================
+    //                                                                              JSP
+    //                                                                           =========
+
+    protected HtmlResponse asDictIndexHtml() {
+        return redirect(AdminDictAction.class);
+    }
+
+    private HtmlResponse asListHtml(final String dictId) {
+        return asHtml(path_AdminDictProtwords_AdminDictProtwordsJsp).renderWith(data -> {
+            RenderDataUtil.register(data, "protwordsItemItems", protwordsService.getProtwordsList(dictId, protwordsPager));
+        }).useForm(SearchForm.class, setup -> {
+            setup.setup(form -> {
+                copyBeanToBean(protwordsPager, form, op -> op.include("id"));
+            });
+        });
+    }
+
+    private HtmlResponse asEditHtml() {
+        return asHtml(path_AdminDictProtwords_AdminDictProtwordsEditJsp);
+    }
+
+    private HtmlResponse asDetailsHtml() {
+        return asHtml(path_AdminDictProtwords_AdminDictProtwordsDetailsJsp);
+    }
+
+}

+ 42 - 0
src/main/java/org/codelibs/fess/app/web/admin/dict/protwords/CreateForm.java

@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.admin.dict.protwords;
+
+import javax.validation.constraints.Size;
+
+import org.codelibs.fess.app.web.CrudMode;
+import org.lastaflute.web.validation.Required;
+import org.lastaflute.web.validation.theme.conversion.ValidateTypeFailure;
+
+/**
+ * @author ma2tani
+ */
+public class CreateForm {
+
+    @Required
+    public String dictId;
+
+    @ValidateTypeFailure
+    public Integer crudMode;
+
+    @Required
+    @Size(max = 1000)
+    public String input;
+
+    public void initialize() {
+        crudMode = CrudMode.CREATE;
+    }
+}

+ 23 - 0
src/main/java/org/codelibs/fess/app/web/admin/dict/protwords/DownloadForm.java

@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.admin.dict.protwords;
+
+import org.lastaflute.web.validation.Required;
+
+public class DownloadForm {
+    @Required
+    public String dictId;
+}

+ 33 - 0
src/main/java/org/codelibs/fess/app/web/admin/dict/protwords/EditForm.java

@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.admin.dict.protwords;
+
+import org.lastaflute.web.validation.Required;
+import org.lastaflute.web.validation.theme.conversion.ValidateTypeFailure;
+
+/**
+ * @author ma2tani
+ */
+public class EditForm extends CreateForm {
+
+    @Required
+    @ValidateTypeFailure
+    public Long id;
+
+    public String getDisplayId() {
+        return dictId + ":" + id;
+    }
+}

+ 27 - 0
src/main/java/org/codelibs/fess/app/web/admin/dict/protwords/SearchForm.java

@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.admin.dict.protwords;
+
+import org.lastaflute.web.validation.Required;
+
+/**
+ * @author ma2tani
+ */
+public class SearchForm {
+
+    @Required
+    public String dictId;
+}

+ 32 - 0
src/main/java/org/codelibs/fess/app/web/admin/dict/protwords/UploadForm.java

@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.admin.dict.protwords;
+
+import org.lastaflute.web.ruts.multipart.MultipartFormFile;
+import org.lastaflute.web.validation.Required;
+
+/**
+ * @author ma2tani
+ */
+public class UploadForm {
+
+    @Required
+    public String dictId;
+
+    @Required
+    public MultipartFormFile protwordsFile;
+
+}

+ 3 - 1
src/main/java/org/codelibs/fess/dict/DictionaryManager.java

@@ -30,6 +30,7 @@ import org.codelibs.core.io.FileUtil;
 import org.codelibs.elasticsearch.runner.net.Curl;
 import org.codelibs.elasticsearch.runner.net.Curl;
 import org.codelibs.elasticsearch.runner.net.CurlResponse;
 import org.codelibs.elasticsearch.runner.net.CurlResponse;
 import org.codelibs.fess.Constants;
 import org.codelibs.fess.Constants;
+import org.codelibs.fess.util.ComponentUtil;
 import org.codelibs.fess.util.ResourceUtil;
 import org.codelibs.fess.util.ResourceUtil;
 import org.dbflute.optional.OptionalEntity;
 import org.dbflute.optional.OptionalEntity;
 import org.slf4j.Logger;
 import org.slf4j.Logger;
@@ -49,7 +50,8 @@ public class DictionaryManager {
 
 
     public DictionaryFile<? extends DictionaryItem>[] getDictionaryFiles() {
     public DictionaryFile<? extends DictionaryItem>[] getDictionaryFiles() {
         try (CurlResponse response =
         try (CurlResponse response =
-                Curl.get(ResourceUtil.getElasticsearchHttpUrl() + "/_configsync/file").param("fields", "path,@timestamp").execute()) {
+                Curl.get(ResourceUtil.getElasticsearchHttpUrl() + "/_configsync/file").param("fields", "path,@timestamp")
+                        .param("size", ComponentUtil.getFessConfig().getPageDictionaryMaxFetchSize()).execute()) {
             final Map<String, Object> contentMap = response.getContentAsMap();
             final Map<String, Object> contentMap = response.getContentAsMap();
             @SuppressWarnings("unchecked")
             @SuppressWarnings("unchecked")
             final List<Map<String, Object>> fileList = (List<Map<String, Object>>) contentMap.get("file");
             final List<Map<String, Object>> fileList = (List<Map<String, Object>>) contentMap.get("file");

+ 38 - 0
src/main/java/org/codelibs/fess/dict/mapping/CharMappingCreator.java

@@ -0,0 +1,38 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.dict.mapping;
+
+import java.util.Date;
+
+import org.codelibs.fess.dict.DictionaryCreator;
+import org.codelibs.fess.dict.DictionaryFile;
+import org.codelibs.fess.dict.DictionaryItem;
+
+public class CharMappingCreator extends DictionaryCreator {
+
+    public CharMappingCreator() {
+        super("mapping.*\\.txt");
+    }
+
+    public CharMappingCreator(final String pattern) {
+        super(pattern);
+    }
+
+    @Override
+    protected DictionaryFile<? extends DictionaryItem> newDictionaryFile(final String id, final String path, final Date timestamp) {
+        return new CharMappingFile(id, path, timestamp).manager(dictionaryManager);
+    }
+}

+ 295 - 0
src/main/java/org/codelibs/fess/dict/mapping/CharMappingFile.java

@@ -0,0 +1,295 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.dict.mapping;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.io.IOUtils;
+import org.codelibs.core.lang.StringUtil;
+import org.codelibs.fess.Constants;
+import org.codelibs.fess.dict.DictionaryException;
+import org.codelibs.fess.dict.DictionaryFile;
+import org.dbflute.optional.OptionalEntity;
+
+public class CharMappingFile extends DictionaryFile<CharMappingItem> {
+    private static final String MAPPING = "mapping";
+
+    List<CharMappingItem> mappingItemList;
+
+    public CharMappingFile(final String id, final String path, final Date timestamp) {
+        super(id, path, timestamp);
+    }
+
+    @Override
+    public String getType() {
+        return MAPPING;
+    }
+
+    @Override
+    public String getPath() {
+        return path;
+    }
+
+    @Override
+    public OptionalEntity<CharMappingItem> get(long id) {
+        if (mappingItemList == null) {
+            reload(null, null);
+        }
+
+        for (final CharMappingItem mappingItem : mappingItemList) {
+            if (id == mappingItem.getId()) {
+                return OptionalEntity.of(mappingItem);
+            }
+        }
+        return OptionalEntity.empty();
+    }
+
+    @Override
+    public synchronized PagingList<CharMappingItem> selectList(final int offset, final int size) {
+        if (mappingItemList == null) {
+            reload(null, null);
+        }
+
+        if (offset >= mappingItemList.size() || offset < 0) {
+            return new PagingList<>(Collections.<CharMappingItem> emptyList(), offset, size, mappingItemList.size());
+        }
+
+        int toIndex = offset + size;
+        if (toIndex > mappingItemList.size()) {
+            toIndex = mappingItemList.size();
+        }
+
+        return new PagingList<>(mappingItemList.subList(offset, toIndex), offset, size, mappingItemList.size());
+    }
+
+    @Override
+    public synchronized void insert(final CharMappingItem item) {
+        try (MappingUpdater updater = new MappingUpdater(item)) {
+            reload(updater, null);
+        }
+    }
+
+    @Override
+    public synchronized void update(final CharMappingItem item) {
+        try (MappingUpdater updater = new MappingUpdater(item)) {
+            reload(updater, null);
+        }
+    }
+
+    @Override
+    public synchronized void delete(final CharMappingItem item) {
+        final CharMappingItem mappingItem = item;
+        mappingItem.setNewInputs(StringUtil.EMPTY_STRINGS);
+        mappingItem.setNewOutput(StringUtil.EMPTY);
+        try (MappingUpdater updater = new MappingUpdater(item)) {
+            reload(updater, null);
+        }
+    }
+
+    protected void reload(final MappingUpdater updater, final InputStream in) {
+        final Pattern parsePattern = Pattern.compile("(.*)\\s*=>\\s*(.*)\\s*$");
+        final List<CharMappingItem> itemList = new ArrayList<>();
+        try (BufferedReader reader =
+                new BufferedReader(new InputStreamReader(in != null ? in : dictionaryManager.getContentInputStream(this), Constants.UTF_8))) {
+            long id = 0;
+            String line = null;
+            while ((line = reader.readLine()) != null) {
+                // Remove comments
+                line = line.replaceAll("#.*$", StringUtil.EMPTY);
+
+                // Skip empty lines or comment lines
+                if (line.trim().length() == 0) {
+                    if (updater != null) {
+                        updater.write(line);
+                    }
+                    continue;
+                }
+
+                String[] inputs;
+                String output;
+
+                Matcher m = parsePattern.matcher(line.trim());
+
+                if (!m.find()) {
+                    throw new DictionaryException("Failed to parse " + path);
+                }
+
+                inputs = m.group(1).trim().split(",");
+                output = m.group(2).trim();
+
+                if (inputs == null || output == null || inputs.length == 0) {
+                    throw new DictionaryException("Failed to parse " + path);
+                }
+
+                id++;
+                final CharMappingItem item = new CharMappingItem(id, inputs, output);
+
+                if (updater != null) {
+                    final CharMappingItem newItem = updater.write(item);
+                    if (newItem != null) {
+                        itemList.add(newItem);
+                    } else {
+                        id--;
+                    }
+                } else {
+                    itemList.add(item);
+                }
+            }
+            if (updater != null) {
+                final CharMappingItem item = updater.commit();
+                if (item != null) {
+                    itemList.add(item);
+                }
+            }
+            mappingItemList = itemList;
+        } catch (final IOException e) {
+            throw new DictionaryException("Failed to parse " + path, e);
+        }
+    }
+
+    public String getSimpleName() {
+        return new File(path).getName();
+    }
+
+    public InputStream getInputStream() throws IOException {
+        return new BufferedInputStream(dictionaryManager.getContentInputStream(this));
+    }
+
+    public synchronized void update(final InputStream in) throws IOException {
+        try (MappingUpdater updater = new MappingUpdater(null)) {
+            reload(updater, in);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "MappingFile [path=" + path + ", mappingItemList=" + mappingItemList + ", id=" + id + "]";
+    }
+
+    protected class MappingUpdater implements Closeable {
+
+        protected boolean isCommit = false;
+
+        protected File newFile;
+
+        protected Writer writer;
+
+        protected CharMappingItem item;
+
+        protected MappingUpdater(final CharMappingItem newItem) {
+            try {
+                newFile = File.createTempFile(MAPPING, ".txt");
+                writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newFile), Constants.UTF_8));
+            } catch (final IOException e) {
+                if (newFile != null) {
+                    newFile.delete();
+                }
+                throw new DictionaryException("Failed to write a userDict file.", e);
+            }
+            item = newItem;
+        }
+
+        public CharMappingItem write(final CharMappingItem oldItem) {
+            try {
+                if (item != null && item.getId() == oldItem.getId() && item.isUpdated()) {
+                    if (item.equals(oldItem)) {
+                        try {
+                            if (!item.isDeleted()) {
+                                // update
+                                writer.write(item.toLineString());
+                                writer.write(Constants.LINE_SEPARATOR);
+                                return new CharMappingItem(item.getId(), item.getNewInputs(), item.getNewOutput());
+                            } else {
+                                return null;
+                            }
+                        } finally {
+                            item.setNewInputs(null);
+                            item.setNewOutput(null);
+                        }
+                    } else {
+                        throw new DictionaryException("Mapping file was updated: old=" + oldItem + " : new=" + item);
+                    }
+                } else {
+                    writer.write(oldItem.toLineString());
+                    writer.write(Constants.LINE_SEPARATOR);
+                    return oldItem;
+                }
+            } catch (final IOException e) {
+                throw new DictionaryException("Failed to write: " + oldItem + " -> " + item, e);
+            }
+        }
+
+        public void write(final String line) {
+            try {
+                writer.write(line);
+                writer.write(Constants.LINE_SEPARATOR);
+            } catch (final IOException e) {
+                throw new DictionaryException("Failed to write: " + line, e);
+            }
+        }
+
+        public CharMappingItem commit() {
+            isCommit = true;
+            if (item != null && item.isUpdated()) {
+                try {
+                    writer.write(item.toLineString());
+                    writer.write(Constants.LINE_SEPARATOR);
+                    return item;
+                } catch (final IOException e) {
+                    throw new DictionaryException("Failed to write: " + item, e);
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public void close() {
+            try {
+                writer.flush();
+            } catch (final IOException e) {
+                // ignore
+            }
+            IOUtils.closeQuietly(writer);
+
+            if (isCommit) {
+                try {
+                    dictionaryManager.store(CharMappingFile.this, newFile);
+                } finally {
+                    newFile.delete();
+                }
+            } else {
+                newFile.delete();
+            }
+        }
+    }
+
+}

+ 140 - 0
src/main/java/org/codelibs/fess/dict/mapping/CharMappingItem.java

@@ -0,0 +1,140 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.dict.mapping;
+
+import java.util.Arrays;
+
+import org.apache.commons.lang3.StringUtils;
+import org.codelibs.core.lang.StringUtil;
+import org.codelibs.fess.dict.DictionaryItem;
+
+public class CharMappingItem extends DictionaryItem {
+    private final String[] inputs;
+
+    private final String output;
+
+    private String[] newInputs;
+
+    private String newOutput;
+
+    public CharMappingItem(final long id, final String[] inputs, final String output) {
+        this.id = id;
+        this.inputs = inputs;
+        this.output = output;
+        Arrays.sort(inputs);
+
+        if (id == 0) {
+            // create
+            newInputs = inputs;
+            newOutput = output;
+        }
+    }
+
+    public String[] getNewInputs() {
+        return newInputs;
+    }
+
+    public void setNewInputs(final String[] newInputs) {
+        this.newInputs = newInputs;
+    }
+
+    public String getNewOutput() {
+        return newOutput;
+    }
+
+    public void setNewOutput(final String newOutput) {
+        this.newOutput = newOutput;
+    }
+
+    public String[] getInputs() {
+        return inputs;
+    }
+
+    public String getInputsValue() {
+        if (inputs == null) {
+            return StringUtil.EMPTY;
+        }
+        return String.join("\n", inputs);
+    }
+
+    public String getOutput() {
+        return output;
+    }
+
+    public boolean isUpdated() {
+        return newInputs != null && newOutput != null;
+    }
+
+    public boolean isDeleted() {
+        return isUpdated() && newInputs.length == 0;
+    }
+
+    public void sort() {
+        if (inputs != null) {
+            Arrays.sort(inputs);
+        }
+        if (newInputs != null) {
+            Arrays.sort(newInputs);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + Arrays.hashCode(inputs);
+        result = prime * result + (output == null ? 0 : output.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final CharMappingItem other = (CharMappingItem) obj;
+        sort();
+        other.sort();
+        if (!Arrays.equals(inputs, other.inputs)) {
+            return false;
+        }
+        if (!output.equals(other.getOutput())) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "MappingItem [id=" + id + ", inputs=" + Arrays.toString(inputs) + ", output=" + output + ", newInputs="
+                + Arrays.toString(newInputs) + ", newOutput=" + newOutput + "]";
+    }
+
+    public String toLineString() {
+        if (isUpdated()) {
+            return StringUtils.join(newInputs, ",") + "=>" + newOutput;
+        } else {
+            return StringUtils.join(inputs, ",") + "=>" + output;
+        }
+    }
+
+}

+ 39 - 0
src/main/java/org/codelibs/fess/dict/protwords/ProtwordsCreator.java

@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.dict.protwords;
+
+import java.util.Date;
+
+import org.codelibs.fess.dict.DictionaryCreator;
+import org.codelibs.fess.dict.DictionaryFile;
+import org.codelibs.fess.dict.DictionaryItem;
+
+public class ProtwordsCreator extends DictionaryCreator {
+
+    public ProtwordsCreator() {
+        super("protwords.*\\.txt");
+    }
+
+    public ProtwordsCreator(final String pattern) {
+        super(pattern);
+    }
+
+    @Override
+    protected DictionaryFile<? extends DictionaryItem> newDictionaryFile(final String id, final String path, final Date timestamp) {
+        return new ProtwordsFile(id, path, timestamp).manager(dictionaryManager);
+    }
+
+}

+ 292 - 0
src/main/java/org/codelibs/fess/dict/protwords/ProtwordsFile.java

@@ -0,0 +1,292 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.dict.protwords;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.commons.io.IOUtils;
+import org.codelibs.core.lang.StringUtil;
+import org.codelibs.fess.Constants;
+import org.codelibs.fess.dict.DictionaryException;
+import org.codelibs.fess.dict.DictionaryFile;
+import org.dbflute.optional.OptionalEntity;
+
+public class ProtwordsFile extends DictionaryFile<ProtwordsItem> {
+    private static final String PROTWORDS = "protwords";
+
+    List<ProtwordsItem> protwordsItemList;
+
+    public ProtwordsFile(final String id, final String path, final Date timestamp) {
+        super(id, path, timestamp);
+    }
+
+    @Override
+    public String getType() {
+        return PROTWORDS;
+    }
+
+    @Override
+    public String getPath() {
+        return path;
+    }
+
+    @Override
+    public synchronized OptionalEntity<ProtwordsItem> get(final long id) {
+        if (protwordsItemList == null) {
+            reload(null, null);
+        }
+
+        for (final ProtwordsItem ProtwordsItem : protwordsItemList) {
+            if (id == ProtwordsItem.getId()) {
+                return OptionalEntity.of(ProtwordsItem);
+            }
+        }
+        return OptionalEntity.empty();
+    }
+
+    @Override
+    public synchronized PagingList<ProtwordsItem> selectList(final int offset, final int size) {
+        if (protwordsItemList == null) {
+            reload(null, null);
+        }
+
+        if (offset >= protwordsItemList.size() || offset < 0) {
+            return new PagingList<>(Collections.<ProtwordsItem> emptyList(), offset, size, protwordsItemList.size());
+        }
+
+        int toIndex = offset + size;
+        if (toIndex > protwordsItemList.size()) {
+            toIndex = protwordsItemList.size();
+        }
+
+        return new PagingList<>(protwordsItemList.subList(offset, toIndex), offset, size, protwordsItemList.size());
+    }
+
+    @Override
+    public synchronized void insert(final ProtwordsItem item) {
+        try (SynonymUpdater updater = new SynonymUpdater(item)) {
+            reload(updater, null);
+        }
+    }
+
+    @Override
+    public synchronized void update(final ProtwordsItem item) {
+        try (SynonymUpdater updater = new SynonymUpdater(item)) {
+            reload(updater, null);
+        }
+    }
+
+    @Override
+    public synchronized void delete(final ProtwordsItem item) {
+        final ProtwordsItem ProtwordsItem = item;
+        ProtwordsItem.setNewInput(StringUtil.EMPTY);
+        try (SynonymUpdater updater = new SynonymUpdater(item)) {
+            reload(updater, null);
+        }
+    }
+
+    protected void reload(final SynonymUpdater updater, final InputStream in) {
+        final List<ProtwordsItem> itemList = new ArrayList<>();
+        try (BufferedReader reader =
+                new BufferedReader(new InputStreamReader(in != null ? in : dictionaryManager.getContentInputStream(this), Constants.UTF_8))) {
+            long id = 0;
+            String line = null;
+            while ((line = reader.readLine()) != null) {
+                if (line.length() == 0 || line.charAt(0) == '#') {
+                    if (updater != null) {
+                        updater.write(line);
+                    }
+                    continue; // ignore empty lines and comments
+                }
+
+                final String inputStrings = line;
+                String input = null;
+                if (inputStrings != null) {
+                    input = unescape(inputStrings);
+                }
+
+                if (input.length() > 0) {
+                    id++;
+                    final ProtwordsItem item = new ProtwordsItem(id, input);
+                    if (updater != null) {
+                        final ProtwordsItem newItem = updater.write(item);
+                        if (newItem != null) {
+                            itemList.add(newItem);
+                        } else {
+                            id--;
+                        }
+                    } else {
+                        itemList.add(item);
+                    }
+                }
+            }
+            if (updater != null) {
+                final ProtwordsItem item = updater.commit();
+                if (item != null) {
+                    itemList.add(item);
+                }
+            }
+            protwordsItemList = itemList;
+        } catch (final IOException e) {
+            throw new DictionaryException("Failed to parse " + path, e);
+        }
+    }
+
+    private String unescape(final String s) {
+        if (s.indexOf('\\') >= 0) {
+            final StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < s.length(); i++) {
+                final char ch = s.charAt(i);
+                if (ch == '\\' && i < s.length() - 1) {
+                    sb.append(s.charAt(++i));
+                } else {
+                    sb.append(ch);
+                }
+            }
+            return sb.toString();
+        }
+        return s;
+    }
+
+    public String getSimpleName() {
+        return new File(path).getName();
+    }
+
+    public InputStream getInputStream() throws IOException {
+        return new BufferedInputStream(dictionaryManager.getContentInputStream(this));
+    }
+
+    public synchronized void update(final InputStream in) throws IOException {
+        try (SynonymUpdater updater = new SynonymUpdater(null)) {
+            reload(updater, in);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "SynonymFile [path=" + path + ", srotwordsItemList=" + protwordsItemList + ", id=" + id + "]";
+    }
+
+    protected class SynonymUpdater implements Closeable {
+
+        protected boolean isCommit = false;
+
+        protected File newFile;
+
+        protected Writer writer;
+
+        protected ProtwordsItem item;
+
+        protected SynonymUpdater(final ProtwordsItem newItem) {
+            try {
+                newFile = File.createTempFile(PROTWORDS, ".txt");
+                writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newFile), Constants.UTF_8));
+            } catch (final IOException e) {
+                if (newFile != null) {
+                    newFile.delete();
+                }
+                throw new DictionaryException("Failed to write a userDict file.", e);
+            }
+            item = newItem;
+        }
+
+        public ProtwordsItem write(final ProtwordsItem oldItem) {
+            try {
+                if (item != null && item.getId() == oldItem.getId() && item.isUpdated()) {
+                    if (item.equals(oldItem)) {
+                        try {
+                            if (!item.isDeleted()) {
+                                // update
+                                writer.write(item.toLineString());
+                                writer.write(Constants.LINE_SEPARATOR);
+                                return new ProtwordsItem(item.getId(), item.getNewInput());
+                            } else {
+                                return null;
+                            }
+                        } finally {
+                            item.setNewInput(null);
+                        }
+                    } else {
+                        throw new DictionaryException("Protwords file was updated: old=" + oldItem + " : new=" + item);
+                    }
+                } else {
+                    writer.write(oldItem.toLineString());
+                    writer.write(Constants.LINE_SEPARATOR);
+                    return oldItem;
+                }
+            } catch (final IOException e) {
+                throw new DictionaryException("Failed to write: " + oldItem + " -> " + item, e);
+            }
+        }
+
+        public void write(final String line) {
+            try {
+                writer.write(line);
+                writer.write(Constants.LINE_SEPARATOR);
+            } catch (final IOException e) {
+                throw new DictionaryException("Failed to write: " + line, e);
+            }
+        }
+
+        public ProtwordsItem commit() {
+            isCommit = true;
+            if (item != null && item.isUpdated()) {
+                try {
+                    writer.write(item.toLineString());
+                    writer.write(Constants.LINE_SEPARATOR);
+                    return item;
+                } catch (final IOException e) {
+                    throw new DictionaryException("Failed to write: " + item, e);
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public void close() {
+            try {
+                writer.flush();
+            } catch (final IOException e) {
+                // ignore
+            }
+            IOUtils.closeQuietly(writer);
+
+            if (isCommit) {
+                try {
+                    dictionaryManager.store(ProtwordsFile.this, newFile);
+                } finally {
+                    newFile.delete();
+                }
+            } else {
+                newFile.delete();
+            }
+        }
+    }
+}

+ 103 - 0
src/main/java/org/codelibs/fess/dict/protwords/ProtwordsItem.java

@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.dict.protwords;
+
+import org.apache.commons.lang3.StringUtils;
+import org.codelibs.core.lang.StringUtil;
+import org.codelibs.fess.dict.DictionaryItem;
+
+public class ProtwordsItem extends DictionaryItem {
+    private final String input;
+
+    private String newInput;
+
+    public ProtwordsItem(final long id, final String input) {
+        this.id = id;
+        this.input = input;
+
+        if (id == 0) {
+            // create
+            newInput = input;
+        }
+    }
+
+    public String getNewInput() {
+        return newInput;
+    }
+
+    public void setNewInput(final String newInput) {
+        this.newInput = newInput;
+    }
+
+    public String getInput() {
+        return input;
+    }
+
+    public String getInputValue() {
+        if (input == null) {
+            return StringUtil.EMPTY;
+        }
+        return input;
+    }
+
+    public boolean isUpdated() {
+        return newInput != null;
+    }
+
+    public boolean isDeleted() {
+        return isUpdated() && newInput.length() == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + input.hashCode();
+        return result;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final ProtwordsItem other = (ProtwordsItem) obj;
+        if (!input.equals(other.input)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "ProtwordsItem [id=" + id + ", inputs=" + input + ", newInputs=" + newInput + "]";
+    }
+
+    public String toLineString() {
+        if (isUpdated()) {
+            return StringUtils.join(newInput);
+        } else {
+            return StringUtils.join(input);
+        }
+    }
+
+}

+ 31 - 0
src/main/java/org/codelibs/fess/mylasta/action/FessHtmlPath.java

@@ -92,6 +92,37 @@ public interface FessHtmlPath {
     /** The path of the HTML: /admin/dict/kuromoji/admin_dict_kuromoji_upload.jsp */
     /** The path of the HTML: /admin/dict/kuromoji/admin_dict_kuromoji_upload.jsp */
     HtmlNext path_AdminDictKuromoji_AdminDictKuromojiUploadJsp = new HtmlNext("/admin/dict/kuromoji/admin_dict_kuromoji_upload.jsp");
     HtmlNext path_AdminDictKuromoji_AdminDictKuromojiUploadJsp = new HtmlNext("/admin/dict/kuromoji/admin_dict_kuromoji_upload.jsp");
 
 
+    /** The path of the HTML: /admin/dict/mapping/admin_dict_mapping.jsp */
+    HtmlNext path_AdminDictMapping_AdminDictMappingJsp = new HtmlNext("/admin/dict/mapping/admin_dict_mapping.jsp");
+
+    /** The path of the HTML: /admin/dict/mapping/admin_dict_mapping_details.jsp */
+    HtmlNext path_AdminDictMapping_AdminDictMappingDetailsJsp = new HtmlNext("/admin/dict/mapping/admin_dict_mapping_details.jsp");
+
+    /** The path of the HTML: /admin/dict/mapping/admin_dict_mapping_download.jsp */
+    HtmlNext path_AdminDictMapping_AdminDictMappingDownloadJsp = new HtmlNext("/admin/dict/mapping/admin_dict_mapping_download.jsp");
+
+    /** The path of the HTML: /admin/dict/mapping/admin_dict_mapping_edit.jsp */
+    HtmlNext path_AdminDictMapping_AdminDictMappingEditJsp = new HtmlNext("/admin/dict/mapping/admin_dict_mapping_edit.jsp");
+
+    /** The path of the HTML: /admin/dict/mapping/admin_dict_mapping_upload.jsp */
+    HtmlNext path_AdminDictMapping_AdminDictMappingUploadJsp = new HtmlNext("/admin/dict/mapping/admin_dict_mapping_upload.jsp");
+
+    /** The path of the HTML: /admin/dict/protwords/admin_dict_protwords.jsp */
+    HtmlNext path_AdminDictProtwords_AdminDictProtwordsJsp = new HtmlNext("/admin/dict/protwords/admin_dict_protwords.jsp");
+
+    /** The path of the HTML: /admin/dict/protwords/admin_dict_protwords_details.jsp */
+    HtmlNext path_AdminDictProtwords_AdminDictProtwordsDetailsJsp = new HtmlNext("/admin/dict/protwords/admin_dict_protwords_details.jsp");
+
+    /** The path of the HTML: /admin/dict/protwords/admin_dict_protwords_download.jsp */
+    HtmlNext path_AdminDictProtwords_AdminDictProtwordsDownloadJsp =
+            new HtmlNext("/admin/dict/protwords/admin_dict_protwords_download.jsp");
+
+    /** The path of the HTML: /admin/dict/protwords/admin_dict_protwords_edit.jsp */
+    HtmlNext path_AdminDictProtwords_AdminDictProtwordsEditJsp = new HtmlNext("/admin/dict/protwords/admin_dict_protwords_edit.jsp");
+
+    /** The path of the HTML: /admin/dict/protwords/admin_dict_protwords_upload.jsp */
+    HtmlNext path_AdminDictProtwords_AdminDictProtwordsUploadJsp = new HtmlNext("/admin/dict/protwords/admin_dict_protwords_upload.jsp");
+
     /** The path of the HTML: /admin/dict/seunjeon/admin_dict_seunjeon.jsp */
     /** The path of the HTML: /admin/dict/seunjeon/admin_dict_seunjeon.jsp */
     HtmlNext path_AdminDictSeunjeon_AdminDictSeunjeonJsp = new HtmlNext("/admin/dict/seunjeon/admin_dict_seunjeon.jsp");
     HtmlNext path_AdminDictSeunjeon_AdminDictSeunjeonJsp = new HtmlNext("/admin/dict/seunjeon/admin_dict_seunjeon.jsp");
 
 

+ 84 - 0
src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java

@@ -365,6 +365,9 @@ public class FessLabels extends ActionMessages {
     /** The key of the message: Bad Word File */
     /** The key of the message: Bad Word File */
     public static final String LABELS_BAD_WORD_FILE = "{labels.badWordFile}";
     public static final String LABELS_BAD_WORD_FILE = "{labels.badWordFile}";
 
 
+    /** The key of the message: Mapping File */
+    public static final String LABELS_MAPPING_FILE = "{labels.mappingFile}";
+
     /** The key of the message: Boost Expr */
     /** The key of the message: Boost Expr */
     public static final String LABELS_BOOST_EXPR = "{labels.boostExpr}";
     public static final String LABELS_BOOST_EXPR = "{labels.boostExpr}";
 
 
@@ -1764,6 +1767,48 @@ public class FessLabels extends ActionMessages {
     /** The key of the message: Synonym File */
     /** The key of the message: Synonym File */
     public static final String LABELS_dict_synonym_file = "{labels.dict_synonym_file}";
     public static final String LABELS_dict_synonym_file = "{labels.dict_synonym_file}";
 
 
+    /** The key of the message: Mapping List */
+    public static final String LABELS_dict_mapping_configuration = "{labels.dict_mapping_configuration}";
+
+    /** The key of the message: Mapping List */
+    public static final String LABELS_dict_mapping_title = "{labels.dict_mapping_title}";
+
+    /** The key of the message: List */
+    public static final String LABELS_dict_mapping_list_link = "{labels.dict_mapping_list_link}";
+
+    /** The key of the message: Create New */
+    public static final String LABELS_dict_mapping_link_create = "{labels.dict_mapping_link_create}";
+
+    /** The key of the message: Edit */
+    public static final String LABELS_dict_mapping_link_edit = "{labels.dict_mapping_link_edit}";
+
+    /** The key of the message: Delete */
+    public static final String LABELS_dict_mapping_link_delete = "{labels.dict_mapping_link_delete}";
+
+    /** The key of the message: Details */
+    public static final String LABELS_dict_mapping_link_details = "{labels.dict_mapping_link_details}";
+
+    /** The key of the message: Download */
+    public static final String LABELS_dict_mapping_link_download = "{labels.dict_mapping_link_download}";
+
+    /** The key of the message: Upload */
+    public static final String LABELS_dict_mapping_link_upload = "{labels.dict_mapping_link_upload}";
+
+    /** The key of the message: Source */
+    public static final String LABELS_dict_mapping_source = "{labels.dict_mapping_source}";
+
+    /** The key of the message: Target */
+    public static final String LABELS_dict_mapping_target = "{labels.dict_mapping_target}";
+
+    /** The key of the message: Download */
+    public static final String LABELS_dict_mapping_button_download = "{labels.dict_mapping_button_download}";
+
+    /** The key of the message: Upload */
+    public static final String LABELS_dict_mapping_button_upload = "{labels.dict_mapping_button_upload}";
+
+    /** The key of the message: Mapping File */
+    public static final String LABELS_dict_mapping_file = "{labels.dict_mapping_file}";
+
     /** The key of the message: Seunjeon List */
     /** The key of the message: Seunjeon List */
     public static final String LABELS_dict_seunjeon_configuration = "{labels.dict_seunjeon_configuration}";
     public static final String LABELS_dict_seunjeon_configuration = "{labels.dict_seunjeon_configuration}";
 
 
@@ -1851,6 +1896,45 @@ public class FessLabels extends ActionMessages {
     /** The key of the message: Kuromoji File */
     /** The key of the message: Kuromoji File */
     public static final String LABELS_dict_kuromoji_file = "{labels.dict_kuromoji_file}";
     public static final String LABELS_dict_kuromoji_file = "{labels.dict_kuromoji_file}";
 
 
+    /** The key of the message: Protwords List */
+    public static final String LABELS_dict_protwords_configuration = "{labels.dict_protwords_configuration}";
+
+    /** The key of the message: Protwords List */
+    public static final String LABELS_dict_protwords_title = "{labels.dict_protwords_title}";
+
+    /** The key of the message: List */
+    public static final String LABELS_dict_protwords_list_link = "{labels.dict_protwords_list_link}";
+
+    /** The key of the message: Create New */
+    public static final String LABELS_dict_protwords_link_create = "{labels.dict_protwords_link_create}";
+
+    /** The key of the message: Edit */
+    public static final String LABELS_dict_protwords_link_edit = "{labels.dict_protwords_link_edit}";
+
+    /** The key of the message: Delete */
+    public static final String LABELS_dict_protwords_link_delete = "{labels.dict_protwords_link_delete}";
+
+    /** The key of the message: Details */
+    public static final String LABELS_dict_protwords_link_details = "{labels.dict_protwords_link_details}";
+
+    /** The key of the message: Download */
+    public static final String LABELS_dict_protwords_link_download = "{labels.dict_protwords_link_download}";
+
+    /** The key of the message: Upload */
+    public static final String LABELS_dict_protwords_link_upload = "{labels.dict_protwords_link_upload}";
+
+    /** The key of the message: Source */
+    public static final String LABELS_dict_protwords_source = "{labels.dict_protwords_source}";
+
+    /** The key of the message: Download */
+    public static final String LABELS_dict_protwords_button_download = "{labels.dict_protwords_button_download}";
+
+    /** The key of the message: Upload */
+    public static final String LABELS_dict_protwords_button_upload = "{labels.dict_protwords_button_upload}";
+
+    /** The key of the message: Protwords File */
+    public static final String LABELS_dict_protwords_file = "{labels.dict_protwords_file}";
+
     /** The key of the message: Doc Boost */
     /** The key of the message: Doc Boost */
     public static final String LABELS_boost_document_rule_configuration = "{labels.boost_document_rule_configuration}";
     public static final String LABELS_boost_document_rule_configuration = "{labels.boost_document_rule_configuration}";
 
 

+ 85 - 0
src/main/java/org/codelibs/fess/mylasta/action/FessMessages.java

@@ -242,6 +242,12 @@ public class FessMessages extends FessLabels {
     /** The key of the message: Failed to upload the Kuromoji file. */
     /** The key of the message: Failed to upload the Kuromoji file. */
     public static final String ERRORS_failed_to_upload_kuromoji_file = "{errors.failed_to_upload_kuromoji_file}";
     public static final String ERRORS_failed_to_upload_kuromoji_file = "{errors.failed_to_upload_kuromoji_file}";
 
 
+    /** The key of the message: Failed to download the Protwords file. */
+    public static final String ERRORS_failed_to_download_protwords_file = "{errors.failed_to_download_protwords_file}";
+
+    /** The key of the message: Failed to upload the Protwords file. */
+    public static final String ERRORS_failed_to_upload_protwords_file = "{errors.failed_to_upload_protwords_file}";
+
     /** The key of the message: Failed to download the Elevate file. */
     /** The key of the message: Failed to download the Elevate file. */
     public static final String ERRORS_failed_to_download_elevate_file = "{errors.failed_to_download_elevate_file}";
     public static final String ERRORS_failed_to_download_elevate_file = "{errors.failed_to_download_elevate_file}";
 
 
@@ -254,6 +260,12 @@ public class FessMessages extends FessLabels {
     /** The key of the message: Failed to upload the Badword file. */
     /** The key of the message: Failed to upload the Badword file. */
     public static final String ERRORS_failed_to_upload_badword_file = "{errors.failed_to_upload_badword_file}";
     public static final String ERRORS_failed_to_upload_badword_file = "{errors.failed_to_upload_badword_file}";
 
 
+    /** The key of the message: Failed to download the Mapping file. */
+    public static final String ERRORS_failed_to_download_mapping_file = "{errors.failed_to_download_mapping_file}";
+
+    /** The key of the message: Failed to upload the Mapping file. */
+    public static final String ERRORS_failed_to_upload_mapping_file = "{errors.failed_to_upload_mapping_file}";
+
     /** The key of the message: "{1}" in "{0}" is invalid. */
     /** The key of the message: "{1}" in "{0}" is invalid. */
     public static final String ERRORS_invalid_str_is_included = "{errors.invalid_str_is_included}";
     public static final String ERRORS_invalid_str_is_included = "{errors.invalid_str_is_included}";
 
 
@@ -371,6 +383,9 @@ public class FessMessages extends FessLabels {
     /** The key of the message: Uploaded Bad Word file. */
     /** The key of the message: Uploaded Bad Word file. */
     public static final String SUCCESS_upload_bad_word = "{success.upload_bad_word}";
     public static final String SUCCESS_upload_bad_word = "{success.upload_bad_word}";
 
 
+    /** The key of the message: Uploaded Mapping file. */
+    public static final String SUCCESS_upload_mapping_file = "{success.upload_mapping_file}";
+
     /** The key of the message: Sent the test mail. */
     /** The key of the message: Sent the test mail. */
     public static final String SUCCESS_send_testmail = "{success.send_testmail}";
     public static final String SUCCESS_send_testmail = "{success.send_testmail}";
 
 
@@ -1469,6 +1484,34 @@ public class FessMessages extends FessLabels {
         return this;
         return this;
     }
     }
 
 
+    /**
+     * Add the created action message for the key 'errors.failed_to_download_protwords_file' with parameters.
+     * <pre>
+     * message: Failed to download the Protwords file.
+     * </pre>
+     * @param property The property name for the message. (NotNull)
+     * @return this. (NotNull)
+     */
+    public FessMessages addErrorsFailedToDownloadProtwordsFile(String property) {
+        assertPropertyNotNull(property);
+        add(property, new ActionMessage(ERRORS_failed_to_download_protwords_file));
+        return this;
+    }
+
+    /**
+     * Add the created action message for the key 'errors.failed_to_upload_protwords_file' with parameters.
+     * <pre>
+     * message: Failed to upload the Protwords file.
+     * </pre>
+     * @param property The property name for the message. (NotNull)
+     * @return this. (NotNull)
+     */
+    public FessMessages addErrorsFailedToUploadProtwordsFile(String property) {
+        assertPropertyNotNull(property);
+        add(property, new ActionMessage(ERRORS_failed_to_upload_protwords_file));
+        return this;
+    }
+
     /**
     /**
      * Add the created action message for the key 'errors.failed_to_download_elevate_file' with parameters.
      * Add the created action message for the key 'errors.failed_to_download_elevate_file' with parameters.
      * <pre>
      * <pre>
@@ -1525,6 +1568,34 @@ public class FessMessages extends FessLabels {
         return this;
         return this;
     }
     }
 
 
+    /**
+     * Add the created action message for the key 'errors.failed_to_download_mapping_file' with parameters.
+     * <pre>
+     * message: Failed to download the Mapping file.
+     * </pre>
+     * @param property The property name for the message. (NotNull)
+     * @return this. (NotNull)
+     */
+    public FessMessages addErrorsFailedToDownloadMappingFile(String property) {
+        assertPropertyNotNull(property);
+        add(property, new ActionMessage(ERRORS_failed_to_download_mapping_file));
+        return this;
+    }
+
+    /**
+     * Add the created action message for the key 'errors.failed_to_upload_mapping_file' with parameters.
+     * <pre>
+     * message: Failed to upload the Mapping file.
+     * </pre>
+     * @param property The property name for the message. (NotNull)
+     * @return this. (NotNull)
+     */
+    public FessMessages addErrorsFailedToUploadMappingFile(String property) {
+        assertPropertyNotNull(property);
+        add(property, new ActionMessage(ERRORS_failed_to_upload_mapping_file));
+        return this;
+    }
+
     /**
     /**
      * Add the created action message for the key 'errors.invalid_str_is_included' with parameters.
      * Add the created action message for the key 'errors.invalid_str_is_included' with parameters.
      * <pre>
      * <pre>
@@ -2092,6 +2163,20 @@ public class FessMessages extends FessLabels {
         return this;
         return this;
     }
     }
 
 
+    /**
+     * Add the created action message for the key 'success.upload_mapping_file' with parameters.
+     * <pre>
+     * message: Uploaded Mapping file.
+     * </pre>
+     * @param property The property name for the message. (NotNull)
+     * @return this. (NotNull)
+     */
+    public FessMessages addSuccessUploadMappingFile(String property) {
+        assertPropertyNotNull(property);
+        add(property, new ActionMessage(SUCCESS_upload_mapping_file));
+        return this;
+    }
+
     /**
     /**
      * Add the created action message for the key 'success.send_testmail' with parameters.
      * Add the created action message for the key 'success.send_testmail' with parameters.
      * <pre>
      * <pre>

+ 54 - 0
src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java

@@ -565,6 +565,9 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /** The key of the configuration. e.g. 1000 */
     /** The key of the configuration. e.g. 1000 */
     String PAGE_BAD_WORD_MAX_FETCH_SIZE = "page.bad.word.max.fetch.size";
     String PAGE_BAD_WORD_MAX_FETCH_SIZE = "page.bad.word.max.fetch.size";
 
 
+    /** The key of the configuration. e.g. 1000 */
+    String PAGE_DICTIONARY_MAX_FETCH_SIZE = "page.dictionary.max.fetch.size";
+
     /** The key of the configuration. e.g. 0 */
     /** The key of the configuration. e.g. 0 */
     String PAGING_SEARCH_PAGE_START = "paging.search.page.start";
     String PAGING_SEARCH_PAGE_START = "paging.search.page.start";
 
 
@@ -616,6 +619,12 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /** The key of the configuration. e.g. seunjeon */
     /** The key of the configuration. e.g. seunjeon */
     String ONLINE_HELP_NAME_DICT_SEUNJEON = "online.help.name.dict.seunjeon";
     String ONLINE_HELP_NAME_DICT_SEUNJEON = "online.help.name.dict.seunjeon";
 
 
+    /** The key of the configuration. e.g. protwords */
+    String ONLINE_HELP_NAME_DICT_PROTWORDS = "online.help.name.dict.protwords";
+
+    /** The key of the configuration. e.g. mapping */
+    String ONLINE_HELP_NAME_DICT_MAPPING = "online.help.name.dict.mapping";
+
     /** The key of the configuration. e.g. webconfig */
     /** The key of the configuration. e.g. webconfig */
     String ONLINE_HELP_NAME_WEBCONFIG = "online.help.name.webconfig";
     String ONLINE_HELP_NAME_WEBCONFIG = "online.help.name.webconfig";
 
 
@@ -2676,6 +2685,21 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
      */
      */
     Integer getPageBadWordMaxFetchSizeAsInteger();
     Integer getPageBadWordMaxFetchSizeAsInteger();
 
 
+    /**
+     * Get the value for the key 'page.dictionary.max.fetch.size'. <br>
+     * The value is, e.g. 1000 <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getPageDictionaryMaxFetchSize();
+
+    /**
+     * Get the value for the key 'page.dictionary.max.fetch.size' as {@link Integer}. <br>
+     * The value is, e.g. 1000 <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     * @throws NumberFormatException When the property is not integer.
+     */
+    Integer getPageDictionaryMaxFetchSizeAsInteger();
+
     /**
     /**
      * Get the value for the key 'paging.search.page.start'. <br>
      * Get the value for the key 'paging.search.page.start'. <br>
      * The value is, e.g. 0 <br>
      * The value is, e.g. 0 <br>
@@ -2841,6 +2865,20 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
      */
      */
     String getOnlineHelpNameDictSeunjeon();
     String getOnlineHelpNameDictSeunjeon();
 
 
+    /**
+     * Get the value for the key 'online.help.name.dict.protwords'. <br>
+     * The value is, e.g. protwords <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getOnlineHelpNameDictProtwords();
+
+    /**
+     * Get the value for the key 'online.help.name.dict.mapping'. <br>
+     * The value is, e.g. mapping <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getOnlineHelpNameDictMapping();
+
     /**
     /**
      * Get the value for the key 'online.help.name.webconfig'. <br>
      * Get the value for the key 'online.help.name.webconfig'. <br>
      * The value is, e.g. webconfig <br>
      * The value is, e.g. webconfig <br>
@@ -4589,6 +4627,14 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             return getAsInteger(FessConfig.PAGE_BAD_WORD_MAX_FETCH_SIZE);
             return getAsInteger(FessConfig.PAGE_BAD_WORD_MAX_FETCH_SIZE);
         }
         }
 
 
+        public String getPageDictionaryMaxFetchSize() {
+            return get(FessConfig.PAGE_DICTIONARY_MAX_FETCH_SIZE);
+        }
+
+        public Integer getPageDictionaryMaxFetchSizeAsInteger() {
+            return getAsInteger(FessConfig.PAGE_DICTIONARY_MAX_FETCH_SIZE);
+        }
+
         public String getPagingSearchPageStart() {
         public String getPagingSearchPageStart() {
             return get(FessConfig.PAGING_SEARCH_PAGE_START);
             return get(FessConfig.PAGING_SEARCH_PAGE_START);
         }
         }
@@ -4677,6 +4723,14 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             return get(FessConfig.ONLINE_HELP_NAME_DICT_SEUNJEON);
             return get(FessConfig.ONLINE_HELP_NAME_DICT_SEUNJEON);
         }
         }
 
 
+        public String getOnlineHelpNameDictProtwords() {
+            return get(FessConfig.ONLINE_HELP_NAME_DICT_PROTWORDS);
+        }
+
+        public String getOnlineHelpNameDictMapping() {
+            return get(FessConfig.ONLINE_HELP_NAME_DICT_MAPPING);
+        }
+
         public String getOnlineHelpNameWebconfig() {
         public String getOnlineHelpNameWebconfig() {
             return get(FessConfig.ONLINE_HELP_NAME_WEBCONFIG);
             return get(FessConfig.ONLINE_HELP_NAME_WEBCONFIG);
         }
         }

+ 3 - 0
src/main/resources/fess_config.properties

@@ -315,6 +315,7 @@ page.scheduled.job.max.fetch.size=100
 page.search.field.log.max.fetch.size=100
 page.search.field.log.max.fetch.size=100
 page.elevate.word.max.fetch.size=1000
 page.elevate.word.max.fetch.size=1000
 page.bad.word.max.fetch.size=1000
 page.bad.word.max.fetch.size=1000
+page.dictionary.max.fetch.size=1000
 
 
 # search page
 # search page
 paging.search.page.start=0
 paging.search.page.start=0
@@ -347,6 +348,8 @@ online.help.name.dict.synonym=synonym
 online.help.name.dict=dict
 online.help.name.dict=dict
 online.help.name.dict.kuromoji=kuromoji
 online.help.name.dict.kuromoji=kuromoji
 online.help.name.dict.seunjeon=seunjeon
 online.help.name.dict.seunjeon=seunjeon
+online.help.name.dict.protwords=protwords
+online.help.name.dict.mapping=mapping
 online.help.name.webconfig=webconfig
 online.help.name.webconfig=webconfig
 online.help.name.searchlist=searchlist
 online.help.name.searchlist=searchlist
 online.help.name.log=log
 online.help.name.log=log

+ 13 - 1
src/main/resources/fess_dict.xml

@@ -2,7 +2,7 @@
 <!DOCTYPE components PUBLIC "-//DBFLUTE//DTD LastaDi 1.0//EN"
 <!DOCTYPE components PUBLIC "-//DBFLUTE//DTD LastaDi 1.0//EN"
 	"http://dbflute.org/meta/lastadi10.dtd">
 	"http://dbflute.org/meta/lastadi10.dtd">
 <components>
 <components>
-	<include path="fess_config.xml"/>
+	<include path="fess_config.xml" />
 
 
 	<component name="dictionaryManager" class="org.codelibs.fess.dict.DictionaryManager">
 	<component name="dictionaryManager" class="org.codelibs.fess.dict.DictionaryManager">
 		<postConstruct name="addCreator">
 		<postConstruct name="addCreator">
@@ -14,6 +14,12 @@
 		<postConstruct name="addCreator">
 		<postConstruct name="addCreator">
 			<arg>synonymCreator</arg>
 			<arg>synonymCreator</arg>
 		</postConstruct>
 		</postConstruct>
+		<postConstruct name="addCreator">
+			<arg>protwordsCreator</arg>
+		</postConstruct>
+		<postConstruct name="addCreator">
+			<arg>charMappingCreator</arg>
+		</postConstruct>
 	</component>
 	</component>
 
 
 	<component name="kuromojiDictCreator"
 	<component name="kuromojiDictCreator"
@@ -25,4 +31,10 @@
 	<component name="synonymCreator"
 	<component name="synonymCreator"
 		class="org.codelibs.fess.dict.synonym.SynonymCreator">
 		class="org.codelibs.fess.dict.synonym.SynonymCreator">
 	</component>
 	</component>
+	<component name="protwordsCreator"
+		class="org.codelibs.fess.dict.protwords.ProtwordsCreator">
+	</component>
+	<component name="charMappingCreator"
+		class="org.codelibs.fess.dict.mapping.CharMappingCreator">
+	</component>
 </components>
 </components>

+ 28 - 0
src/main/resources/fess_label.properties

@@ -111,6 +111,7 @@ labels.token=Token
 labels.synonymFile=Synonym File
 labels.synonymFile=Synonym File
 labels.elevateWordFile=Additional Word File
 labels.elevateWordFile=Additional Word File
 labels.badWordFile=Bad Word File
 labels.badWordFile=Bad Word File
+labels.mappingFile=Mapping File
 labels.boostExpr=Boost Expr
 labels.boostExpr=Boost Expr
 labels.confirmPassword=Confirm
 labels.confirmPassword=Confirm
 labels.crawler=Crawler
 labels.crawler=Crawler
@@ -578,6 +579,20 @@ labels.dict_synonym_target=Target
 labels.dict_synonym_button_download=Download
 labels.dict_synonym_button_download=Download
 labels.dict_synonym_button_upload=Upload
 labels.dict_synonym_button_upload=Upload
 labels.dict_synonym_file=Synonym File
 labels.dict_synonym_file=Synonym File
+labels.dict_mapping_configuration=Mapping List
+labels.dict_mapping_title=Mapping List
+labels.dict_mapping_list_link=List
+labels.dict_mapping_link_create=Create New
+labels.dict_mapping_link_edit=Edit
+labels.dict_mapping_link_delete=Delete
+labels.dict_mapping_link_details=Details
+labels.dict_mapping_link_download=Download
+labels.dict_mapping_link_upload=Upload
+labels.dict_mapping_source=Source
+labels.dict_mapping_target=Target
+labels.dict_mapping_button_download=Download
+labels.dict_mapping_button_upload=Upload
+labels.dict_mapping_file=Mapping File
 labels.dict_seunjeon_configuration = Seunjeon List
 labels.dict_seunjeon_configuration = Seunjeon List
 labels.dict_seunjeon_title = Seunjeon List
 labels.dict_seunjeon_title = Seunjeon List
 labels.dict_seunjeon_list_link = List
 labels.dict_seunjeon_list_link = List
@@ -607,6 +622,19 @@ labels.dict_kuromoji_pos=POS
 labels.dict_kuromoji_button_download=Download
 labels.dict_kuromoji_button_download=Download
 labels.dict_kuromoji_button_upload=Upload
 labels.dict_kuromoji_button_upload=Upload
 labels.dict_kuromoji_file=Kuromoji File
 labels.dict_kuromoji_file=Kuromoji File
+labels.dict_protwords_configuration=Protwords List
+labels.dict_protwords_title = Protwords List
+labels.dict_protwords_list_link = List
+labels.dict_protwords_link_create = Create New
+labels.dict_protwords_link_edit = Edit
+labels.dict_protwords_link_delete = Delete
+labels.dict_protwords_link_details = Details
+labels.dict_protwords_link_download = Download
+labels.dict_protwords_link_upload = Upload
+labels.dict_protwords_source = Source
+labels.dict_protwords_button_download = Download
+labels.dict_protwords_button_upload = Upload
+labels.dict_protwords_file = Protwords File
 labels.boost_document_rule_configuration=Doc Boost
 labels.boost_document_rule_configuration=Doc Boost
 labels.boost_document_rule_title_details=Doc Boost
 labels.boost_document_rule_title_details=Doc Boost
 labels.boost_document_rule_list_url_expr=Condition
 labels.boost_document_rule_list_url_expr=Condition

+ 28 - 0
src/main/resources/fess_label_en.properties

@@ -111,6 +111,7 @@ labels.token=Token
 labels.synonymFile=Synonym File
 labels.synonymFile=Synonym File
 labels.elevateWordFile=Additional Word File
 labels.elevateWordFile=Additional Word File
 labels.badWordFile=Bad Word File
 labels.badWordFile=Bad Word File
+labels.mappingFile=Mapping File
 labels.boostExpr=Boost Expr
 labels.boostExpr=Boost Expr
 labels.confirmPassword=Confirm
 labels.confirmPassword=Confirm
 labels.crawler=Crawler
 labels.crawler=Crawler
@@ -578,6 +579,20 @@ labels.dict_synonym_target=Target
 labels.dict_synonym_button_download=Download
 labels.dict_synonym_button_download=Download
 labels.dict_synonym_button_upload=Upload
 labels.dict_synonym_button_upload=Upload
 labels.dict_synonym_file=Synonym File
 labels.dict_synonym_file=Synonym File
+labels.dict_mapping_configuration=Mapping List
+labels.dict_mapping_title=Mapping List
+labels.dict_mapping_list_link=List
+labels.dict_mapping_link_create=Create New
+labels.dict_mapping_link_edit=Edit
+labels.dict_mapping_link_delete=Delete
+labels.dict_mapping_link_details=Details
+labels.dict_mapping_link_download=Download
+labels.dict_mapping_link_upload=Upload
+labels.dict_mapping_source=Source
+labels.dict_mapping_target=Target
+labels.dict_mapping_button_download=Download
+labels.dict_mapping_button_upload=Upload
+labels.dict_mapping_file=Mapping File
 labels.dict_seunjeon_configuration = Seunjeon List
 labels.dict_seunjeon_configuration = Seunjeon List
 labels.dict_seunjeon_title = Seunjeon List
 labels.dict_seunjeon_title = Seunjeon List
 labels.dict_seunjeon_list_link = List
 labels.dict_seunjeon_list_link = List
@@ -607,6 +622,19 @@ labels.dict_kuromoji_pos=POS
 labels.dict_kuromoji_button_download=Download
 labels.dict_kuromoji_button_download=Download
 labels.dict_kuromoji_button_upload=Upload
 labels.dict_kuromoji_button_upload=Upload
 labels.dict_kuromoji_file=Kuromoji File
 labels.dict_kuromoji_file=Kuromoji File
+labels.dict_protwords_configuration=Protwords List
+labels.dict_protwords_title = Protwords List
+labels.dict_protwords_list_link = List
+labels.dict_protwords_link_create = Create New
+labels.dict_protwords_link_edit = Edit
+labels.dict_protwords_link_delete = Delete
+labels.dict_protwords_link_details = Details
+labels.dict_protwords_link_download = Download
+labels.dict_protwords_link_upload = Upload
+labels.dict_protwords_source = Source
+labels.dict_protwords_button_download = Download
+labels.dict_protwords_button_upload = Upload
+labels.dict_protwords_file = Protwords File
 labels.boost_document_rule_configuration=Doc Boost
 labels.boost_document_rule_configuration=Doc Boost
 labels.boost_document_rule_title_details=Doc Boost
 labels.boost_document_rule_title_details=Doc Boost
 labels.boost_document_rule_list_url_expr=Condition
 labels.boost_document_rule_list_url_expr=Condition

+ 30 - 0
src/main/resources/fess_label_ja.properties

@@ -111,6 +111,7 @@ labels.token=\u30c8\u30fc\u30af\u30f3
 labels.synonymFile=\u540c\u7fa9\u8a9e\u30d5\u30a1\u30a4\u30eb
 labels.synonymFile=\u540c\u7fa9\u8a9e\u30d5\u30a1\u30a4\u30eb
 labels.elevateWordFile=\u8ffd\u52a0\u30ef\u30fc\u30c9\u30d5\u30a1\u30a4\u30eb
 labels.elevateWordFile=\u8ffd\u52a0\u30ef\u30fc\u30c9\u30d5\u30a1\u30a4\u30eb
 labels.badWordFile=\u9664\u5916\u30ef\u30fc\u30c9\u30d5\u30a1\u30a4\u30eb
 labels.badWordFile=\u9664\u5916\u30ef\u30fc\u30c9\u30d5\u30a1\u30a4\u30eb
+labels.mappingFile=\u30de\u30c3\u30d4\u30f3\u30b0\u30d5\u30a1\u30a4\u30eb
 labels.boostExpr=\u30d6\u30fc\u30b9\u30c8\u5024\u5f0f
 labels.boostExpr=\u30d6\u30fc\u30b9\u30c8\u5024\u5f0f
 labels.confirmPassword=\u78ba\u8a8d
 labels.confirmPassword=\u78ba\u8a8d
 labels.crawler=\u30af\u30ed\u30fc\u30e9
 labels.crawler=\u30af\u30ed\u30fc\u30e9
@@ -574,6 +575,22 @@ labels.dict_synonym_target=\u5909\u63db\u5f8c
 labels.dict_synonym_button_download=\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9
 labels.dict_synonym_button_download=\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9
 labels.dict_synonym_button_upload=\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9
 labels.dict_synonym_button_upload=\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9
 labels.dict_synonym_file=\u540c\u7fa9\u8a9e\u30d5\u30a1\u30a4\u30eb
 labels.dict_synonym_file=\u540c\u7fa9\u8a9e\u30d5\u30a1\u30a4\u30eb
+
+labels.dict_mapping_configuration=\u30de\u30c3\u30d4\u30f3\u30b0\u8f9e\u66f8
+labels.dict_mapping_title=\u30de\u30c3\u30d4\u30f3\u30b0\u8f9e\u66f8
+labels.dict_mapping_list_link=\u4e00\u89a7
+labels.dict_mapping_link_create=\u65b0\u898f\u4f5c\u6210
+labels.dict_mapping_link_edit=\u7de8\u96c6
+labels.dict_mapping_link_delete=\u524a\u9664
+labels.dict_mapping_link_details=\u8a73\u7d30
+labels.dict_mapping_link_download=\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9
+labels.dict_mapping_link_upload=\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9
+labels.dict_mapping_source=\u5909\u63db\u5143
+labels.dict_mapping_target=\u5909\u63db\u5f8c
+labels.dict_mapping_button_download=\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9
+labels.dict_mapping_button_upload=\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9
+labels.dict_mapping_file=\u30de\u30c3\u30d4\u30f3\u30b0\u30d5\u30a1\u30a4\u30eb
+
 labels.dict_seunjeon_configuration = Seunjeon\u5358\u8a9e\u4e00\u89a7
 labels.dict_seunjeon_configuration = Seunjeon\u5358\u8a9e\u4e00\u89a7
 labels.dict_seunjeon_title = Seunjeon\u5358\u8a9e\u4e00\u89a7
 labels.dict_seunjeon_title = Seunjeon\u5358\u8a9e\u4e00\u89a7
 labels.dict_seunjeon_list_link = \u4e00\u89a7
 labels.dict_seunjeon_list_link = \u4e00\u89a7
@@ -603,6 +620,19 @@ labels.dict_kuromoji_pos=\u54c1\u8a5e
 labels.dict_kuromoji_button_download=\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9
 labels.dict_kuromoji_button_download=\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9
 labels.dict_kuromoji_button_upload=\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9
 labels.dict_kuromoji_button_upload=\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9
 labels.dict_kuromoji_file=Kuromoji\u30d5\u30a1\u30a4\u30eb
 labels.dict_kuromoji_file=Kuromoji\u30d5\u30a1\u30a4\u30eb
+labels.dict_protwords_configuration=Protwords\u5358\u8a9e\u4e00\u89a7
+labels.dict_protwords_title = Protwords\u5358\u8a9e\u4e00\u89a7
+labels.dict_protwords_list_link = \u4e00\u89a7
+labels.dict_protwords_link_create = \u65b0\u898f\u4f5c\u6210
+labels.dict_protwords_link_edit = \u7de8\u96c6
+labels.dict_protwords_link_delete = \u524a\u9664
+labels.dict_protwords_link_details = \u8a73\u7d30
+labels.dict_protwords_link_download = \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9
+labels.dict_protwords_link_upload = \u30a2\u30c3\u30d7\u30ed\u30fc\u30c9
+labels.dict_protwords_source = \u5358\u8a9e\u60c5\u5831
+labels.dict_protwords_button_download = \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9
+labels.dict_protwords_button_upload = \u30a2\u30c3\u30d7\u30ed\u30fc\u30c9
+labels.dict_protwords_file = Protwords\u30d5\u30a1\u30a4\u30eb
 labels.boost_document_rule_configuration=\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u30d6\u30fc\u30b9\u30c8
 labels.boost_document_rule_configuration=\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u30d6\u30fc\u30b9\u30c8
 labels.boost_document_rule_title_details=\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u30d6\u30fc\u30b9\u30c8
 labels.boost_document_rule_title_details=\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u30d6\u30fc\u30b9\u30c8
 labels.boost_document_rule_list_url_expr=\u72b6\u614b
 labels.boost_document_rule_list_url_expr=\u72b6\u614b

+ 28 - 0
src/main/resources/fess_label_ko.properties

@@ -109,6 +109,7 @@ labels.token = \ud1a0\ud070
 labels.synonymFile = \ub3d9\uc758\uc5b4 \ud30c\uc77c
 labels.synonymFile = \ub3d9\uc758\uc5b4 \ud30c\uc77c
 labels.elevateWordFile = \ucd94\uac00 \uc6cc\ub4dc \ud30c\uc77c
 labels.elevateWordFile = \ucd94\uac00 \uc6cc\ub4dc \ud30c\uc77c
 labels.badWordFile = \uc81c\uc678 \uc6cc\ub4dc \ud30c\uc77c
 labels.badWordFile = \uc81c\uc678 \uc6cc\ub4dc \ud30c\uc77c
+labels.mappingFile = Mapping \ud30c\uc77c
 labels.boostExpr = \ubd80\uc2a4\ud2b8 \uac12 \uc2dd
 labels.boostExpr = \ubd80\uc2a4\ud2b8 \uac12 \uc2dd
 labels.confirmPassword = \ud655\uc778
 labels.confirmPassword = \ud655\uc778
 labels.crawler = \ud06c\ub864\ub7ec
 labels.crawler = \ud06c\ub864\ub7ec
@@ -569,6 +570,20 @@ labels.dict_synonym_target = \ubcc0\ud658 \ud6c4
 labels.dict_synonym_button_download = \ub2e4\uc6b4\ub85c\ub4dc
 labels.dict_synonym_button_download = \ub2e4\uc6b4\ub85c\ub4dc
 labels.dict_synonym_button_upload = \uc5c5\ub85c\ub4dc
 labels.dict_synonym_button_upload = \uc5c5\ub85c\ub4dc
 labels.dict_synonym_file = \ub3d9\uc758\uc5b4 \ud30c\uc77c
 labels.dict_synonym_file = \ub3d9\uc758\uc5b4 \ud30c\uc77c
+labels.dict_mapping_configuration=Mapping \ubaa9\ub85d
+labels.dict_mapping_title=Mapping \ubaa9\ub85d
+labels.dict_mapping_list_link=\ubaa9\ub85d
+labels.dict_mapping_link_create=\uc0c8\ub85c \ub9cc\ub4e4\uae30
+labels.dict_mapping_link_edit=\ud3b8\uc9d1
+labels.dict_mapping_link_delete=\uc0ad\uc81c
+labels.dict_mapping_link_details=\uc0c1\uc138
+labels.dict_mapping_link_download=\ub2e4\uc6b4\ub85c\ub4dc
+labels.dict_mapping_link_upload=\uc5c5\ub85c\ub4dc
+labels.dict_mapping_source=\uc6d0\ubcf8
+labels.dict_mapping_target=\ubcc0\ud658 \ud6c4
+labels.dict_mapping_button_download=\ub2e4\uc6b4\ub85c\ub4dc
+labels.dict_mapping_button_upload=\uc5c5\ub85c\ub4dc
+labels.dict_mapping_file=Mapping \ud30c\uc77c
 labels.dict_seunjeon_configuration = Seunjeon \ubaa9\ub85d
 labels.dict_seunjeon_configuration = Seunjeon \ubaa9\ub85d
 labels.dict_seunjeon_title = Seunjeon \ubaa9\ub85d
 labels.dict_seunjeon_title = Seunjeon \ubaa9\ub85d
 labels.dict_seunjeon_list_link = \ubaa9\ub85d
 labels.dict_seunjeon_list_link = \ubaa9\ub85d
@@ -598,6 +613,19 @@ labels.dict_kuromoji_pos = \ud488\uc0ac
 labels.dict_kuromoji_button_download = \ub2e4\uc6b4\ub85c\ub4dc
 labels.dict_kuromoji_button_download = \ub2e4\uc6b4\ub85c\ub4dc
 labels.dict_kuromoji_button_upload = \uc5c5\ub85c\ub4dc
 labels.dict_kuromoji_button_upload = \uc5c5\ub85c\ub4dc
 labels.dict_kuromoji_file = Kuromoji \ud30c\uc77c
 labels.dict_kuromoji_file = Kuromoji \ud30c\uc77c
+labels.dict_protwords_configuration = Protwords \ubaa9\ub85d
+labels.dict_protwords_title = Protwords \ubaa9\ub85d
+labels.dict_protwords_list_link = \ubaa9\ub85d
+labels.dict_protwords_link_create = \uc0c8\ub85c \ub9cc\ub4e4\uae30
+labels.dict_protwords_link_edit = \ud3b8\uc9d1
+labels.dict_protwords_link_delete = \uc0ad\uc81c
+labels.dict_protwords_link_details = \uc0c1\uc138
+labels.dict_protwords_link_download = \ub2e4\uc6b4\ub85c\ub4dc
+labels.dict_protwords_link_upload = \uc5c5\ub85c\ub4dc
+labels.dict_protwords_source = \uc6d0\ubcf8
+labels.dict_protwords_button_download = \ub2e4\uc6b4\ub85c\ub4dc
+labels.dict_protwords_button_upload = \uc5c5\ub85c\ub4dc
+labels.dict_protwords_file = Protwords \ud30c\uc77c
 labels.boost_document_rule_configuration = \ubb38\uc11c \ubd80\uc2a4\ud2b8
 labels.boost_document_rule_configuration = \ubb38\uc11c \ubd80\uc2a4\ud2b8
 labels.boost_document_rule_title_details = \ubb38\uc11c \ubd80\uc2a4\ud2b8
 labels.boost_document_rule_title_details = \ubb38\uc11c \ubd80\uc2a4\ud2b8
 labels.boost_document_rule_list_url_expr = \uc0c1\ud0dc
 labels.boost_document_rule_list_url_expr = \uc0c1\ud0dc

+ 5 - 0
src/main/resources/fess_message.properties

@@ -102,10 +102,14 @@ errors.failed_to_download_synonym_file=Failed to download the Synonym file.
 errors.failed_to_upload_synonym_file=Failed to upload the Synonym file.
 errors.failed_to_upload_synonym_file=Failed to upload the Synonym file.
 errors.failed_to_download_kuromoji_file=Failed to download the Kuromoji file.
 errors.failed_to_download_kuromoji_file=Failed to download the Kuromoji file.
 errors.failed_to_upload_kuromoji_file=Failed to upload the Kuromoji file.
 errors.failed_to_upload_kuromoji_file=Failed to upload the Kuromoji file.
+errors.failed_to_download_protwords_file=Failed to download the Protwords file.
+errors.failed_to_upload_protwords_file=Failed to upload the Protwords file.
 errors.failed_to_download_elevate_file=Failed to download the Elevate file.
 errors.failed_to_download_elevate_file=Failed to download the Elevate file.
 errors.failed_to_upload_elevate_file=Failed to upload the Elevate file.
 errors.failed_to_upload_elevate_file=Failed to upload the Elevate file.
 errors.failed_to_download_badword_file=Failed to download the Badword file.
 errors.failed_to_download_badword_file=Failed to download the Badword file.
 errors.failed_to_upload_badword_file=Failed to upload the Badword file.
 errors.failed_to_upload_badword_file=Failed to upload the Badword file.
+errors.failed_to_download_mapping_file=Failed to download the Mapping file.
+errors.failed_to_upload_mapping_file=Failed to upload the Mapping file.
 errors.invalid_str_is_included="{1}" in "{0}" is invalid.
 errors.invalid_str_is_included="{1}" in "{0}" is invalid.
 errors.blank_password=Password is required.
 errors.blank_password=Password is required.
 errors.invalid_confirm_password=Confirm Password does not match.
 errors.invalid_confirm_password=Confirm Password does not match.
@@ -148,6 +152,7 @@ success.upload_synonym_file=Uploaded Synonym file.
 success.upload_kuromoji_file=Uploaded Kuromoji file.
 success.upload_kuromoji_file=Uploaded Kuromoji file.
 success.upload_elevate_word=Uploaded Additional Word file.
 success.upload_elevate_word=Uploaded Additional Word file.
 success.upload_bad_word=Uploaded Bad Word file.
 success.upload_bad_word=Uploaded Bad Word file.
+success.upload_mapping_file=Uploaded Mapping file.
 success.send_testmail=Sent the test mail.
 success.send_testmail=Sent the test mail.
 success.job_log_delete_all=Deleted job logs.
 success.job_log_delete_all=Deleted job logs.
 success.changed_password=Changed your password.
 success.changed_password=Changed your password.

+ 5 - 0
src/main/resources/fess_message_en.properties

@@ -106,6 +106,10 @@ errors.failed_to_download_elevate_file=Failed to download the Elevate file.
 errors.failed_to_upload_elevate_file=Failed to upload the Elevate file.
 errors.failed_to_upload_elevate_file=Failed to upload the Elevate file.
 errors.failed_to_download_badword_file=Failed to download the Badword file.
 errors.failed_to_download_badword_file=Failed to download the Badword file.
 errors.failed_to_upload_badword_file=Failed to upload the Badword file.
 errors.failed_to_upload_badword_file=Failed to upload the Badword file.
+errors.failed_to_download_protwords_file=Failed to download the Protwords file.
+errors.failed_to_upload_protwords_file=Failed to upload the Protwords file.
+errors.failed_to_download_mapping_file=Failed to download the Mapping file.
+errors.failed_to_upload_mapping_file=Failed to upload the Mapping file.
 errors.invalid_str_is_included="{1}" in "{0}" is invalid.
 errors.invalid_str_is_included="{1}" in "{0}" is invalid.
 errors.blank_password=Password is required.
 errors.blank_password=Password is required.
 errors.invalid_confirm_password=Confirm Password does not match.
 errors.invalid_confirm_password=Confirm Password does not match.
@@ -148,6 +152,7 @@ success.upload_synonym_file=Uploaded Synonym file.
 success.upload_kuromoji_file=Uploaded Kuromoji file.
 success.upload_kuromoji_file=Uploaded Kuromoji file.
 success.upload_elevate_word=Uploaded Additional Word file.
 success.upload_elevate_word=Uploaded Additional Word file.
 success.upload_bad_word=Uploaded Bad Word file.
 success.upload_bad_word=Uploaded Bad Word file.
+success.upload_mapping_file=Uploaded Mapping file.
 success.send_testmail=Sent the test mail.
 success.send_testmail=Sent the test mail.
 success.job_log_delete_all=Deleted job logs.
 success.job_log_delete_all=Deleted job logs.
 success.changed_password=Changed your password.
 success.changed_password=Changed your password.

+ 5 - 0
src/main/resources/fess_message_ja.properties

@@ -98,10 +98,14 @@ errors.failed_to_download_synonym_file = \u540c\u7fa9\u8a9e\u30d5\u30a1\u30a4\u3
 errors.failed_to_upload_synonym_file = \u540c\u7fa9\u8a9e\u30d5\u30a1\u30a4\u30eb\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
 errors.failed_to_upload_synonym_file = \u540c\u7fa9\u8a9e\u30d5\u30a1\u30a4\u30eb\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
 errors.failed_to_download_kuromoji_file = Kuromoji\u30d5\u30a1\u30a4\u30eb\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
 errors.failed_to_download_kuromoji_file = Kuromoji\u30d5\u30a1\u30a4\u30eb\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
 errors.failed_to_upload_kuromoji_file = Kuromoji\u30d5\u30a1\u30a4\u30eb\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
 errors.failed_to_upload_kuromoji_file = Kuromoji\u30d5\u30a1\u30a4\u30eb\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
+errors.failed_to_download_protwords_file = Protwords\u30d5\u30a1\u30a4\u30eb\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
+errors.failed_to_upload_protwords_file = Protwords\u30d5\u30a1\u30a4\u30eb\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
 errors.failed_to_download_elevate_file = \u8ffd\u52a0\u30ef\u30fc\u30c9\u30d5\u30a1\u30a4\u30eb\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
 errors.failed_to_download_elevate_file = \u8ffd\u52a0\u30ef\u30fc\u30c9\u30d5\u30a1\u30a4\u30eb\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
 errors.failed_to_upload_elevate_file = \u8ffd\u52a0\u30ef\u30fc\u30c9\u30d5\u30a1\u30a4\u30eb\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
 errors.failed_to_upload_elevate_file = \u8ffd\u52a0\u30ef\u30fc\u30c9\u30d5\u30a1\u30a4\u30eb\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
 errors.failed_to_download_badword_file = \u9664\u5916\u30ef\u30fc\u30c9\u30d5\u30a1\u30a4\u30eb\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
 errors.failed_to_download_badword_file = \u9664\u5916\u30ef\u30fc\u30c9\u30d5\u30a1\u30a4\u30eb\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
 errors.failed_to_upload_badword_file = \u9664\u5916\u30ef\u30fc\u30c9\u30d5\u30a1\u30a4\u30eb\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002
 errors.failed_to_upload_badword_file = \u9664\u5916\u30ef\u30fc\u30c9\u30d5\u30a1\u30a4\u30eb\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002
+errors.failed_to_download_mapping_file = \u30de\u30c3\u30d4\u30f3\u30b0\u30d5\u30a1\u30a4\u30eb\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
+errors.failed_to_upload_mapping_file = \u30de\u30c3\u30d4\u30f3\u30b0\u30d5\u30a1\u30a4\u30eb\u306e\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
 errors.invalid_str_is_included = {0}\u3067\u306f{1}\u306f\u7121\u52b9\u3067\u3059\u3002
 errors.invalid_str_is_included = {0}\u3067\u306f{1}\u306f\u7121\u52b9\u3067\u3059\u3002
 errors.blank_password = \u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u5fc5\u8981\u306b\u306a\u308a\u307e\u3059\u3002
 errors.blank_password = \u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u5fc5\u8981\u306b\u306a\u308a\u307e\u3059\u3002
 errors.invalid_confirm_password = \u30d1\u30b9\u30ef\u30fc\u30c9\u306e\u78ba\u8a8d\u3068\u4e00\u81f4\u3057\u307e\u305b\u3093\u3002
 errors.invalid_confirm_password = \u30d1\u30b9\u30ef\u30fc\u30c9\u306e\u78ba\u8a8d\u3068\u4e00\u81f4\u3057\u307e\u305b\u3093\u3002
@@ -142,6 +146,7 @@ success.upload_synonym_file = \u540c\u7fa9\u8a9e\u30d5\u30a1\u30a4\u30eb\u3092\u
 success.upload_kuromoji_file = Kuromoji\u30d5\u30a1\u30a4\u30eb\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3057\u307e\u3057\u305f\u3002
 success.upload_kuromoji_file = Kuromoji\u30d5\u30a1\u30a4\u30eb\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3057\u307e\u3057\u305f\u3002
 success.upload_elevate_word = \u8ffd\u52a0\u30ef\u30fc\u30c9\u30d5\u30a1\u30a4\u30eb\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3057\u307e\u3057\u305f\u3002
 success.upload_elevate_word = \u8ffd\u52a0\u30ef\u30fc\u30c9\u30d5\u30a1\u30a4\u30eb\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3057\u307e\u3057\u305f\u3002
 success.upload_bad_word = \u9664\u5916\u30ef\u30fc\u30c9\u30d5\u30a1\u30a4\u30eb\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3057\u307e\u3057\u305f\u3002
 success.upload_bad_word = \u9664\u5916\u30ef\u30fc\u30c9\u30d5\u30a1\u30a4\u30eb\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3057\u307e\u3057\u305f\u3002
+success.upload_mapping_file = \u30de\u30c3\u30d4\u30f3\u30b0\u30d5\u30a1\u30a4\u30eb\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3057\u307e\u3057\u305f\u3002
 success.send_testmail=\u30c6\u30b9\u30c8\u30e1\u30fc\u30eb\u3092\u9001\u4fe1\u3057\u307e\u3057\u305f\u3002
 success.send_testmail=\u30c6\u30b9\u30c8\u30e1\u30fc\u30eb\u3092\u9001\u4fe1\u3057\u307e\u3057\u305f\u3002
 success.job_log_delete_all=\u30b8\u30e7\u30d6\u30ed\u30b0\u3092\u524a\u9664\u3057\u307e\u3057\u305f\u3002
 success.job_log_delete_all=\u30b8\u30e7\u30d6\u30ed\u30b0\u3092\u524a\u9664\u3057\u307e\u3057\u305f\u3002
 success.changed_password=\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5909\u66f4\u3057\u307e\u3057\u305f\u3002
 success.changed_password=\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5909\u66f4\u3057\u307e\u3057\u305f\u3002

+ 2 - 0
src/main/resources/fess_message_ko.properties

@@ -98,6 +98,8 @@ errors.failed_to_download_synonym_file = \ub3d9\uc758\uc5b4 \ud30c\uc77c \ub2e4\
 errors.failed_to_upload_synonym_file = \ub3d9\uc758\uc5b4 \ud30c\uc77c\uc744 \uc5c5\ub85c\ub4dc\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4.
 errors.failed_to_upload_synonym_file = \ub3d9\uc758\uc5b4 \ud30c\uc77c\uc744 \uc5c5\ub85c\ub4dc\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4.
 errors.failed_to_download_k\u200b\u200buromoji_file = Kuromoji \ud30c\uc77c \ub2e4\uc6b4\ub85c\ub4dc\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4.
 errors.failed_to_download_k\u200b\u200buromoji_file = Kuromoji \ud30c\uc77c \ub2e4\uc6b4\ub85c\ub4dc\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4.
 errors.failed_to_upload_k\u200b\u200buromoji_file = Kuromoji \ud30c\uc77c \uc5c5\ub85c\ub4dc\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4.
 errors.failed_to_upload_k\u200b\u200buromoji_file = Kuromoji \ud30c\uc77c \uc5c5\ub85c\ub4dc\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4.
+errors.failed_to_download_protwords_file = Protwords \ud30c\uc77c \ub2e4\uc6b4\ub85c\ub4dc\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4.
+errors.failed_to_upload_protwords_file = Protwords \ud30c\uc77c \uc5c5\ub85c\ub4dc\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4.
 errors.failed_to_download_elevate_file = \ucd94\uac00 \uc6cc\ub4dc \ud30c\uc77c \ub2e4\uc6b4\ub85c\ub4dc\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4.
 errors.failed_to_download_elevate_file = \ucd94\uac00 \uc6cc\ub4dc \ud30c\uc77c \ub2e4\uc6b4\ub85c\ub4dc\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4.
 errors.failed_to_upload_elevate_file = \ucd94\uac00 \uc6cc\ub4dc \ud30c\uc77c \uc5c5\ub85c\ub4dc\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4.
 errors.failed_to_upload_elevate_file = \ucd94\uac00 \uc6cc\ub4dc \ud30c\uc77c \uc5c5\ub85c\ub4dc\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4.
 errors.failed_to_download_badword_file = \uc81c\uc678 \uc6cc\ub4dc \ud30c\uc77c \ub2e4\uc6b4\ub85c\ub4dc\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4.
 errors.failed_to_download_badword_file = \uc81c\uc678 \uc6cc\ub4dc \ud30c\uc77c \ub2e4\uc6b4\ub85c\ub4dc\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4.

+ 156 - 0
src/main/webapp/WEB-INF/view/admin/dict/mapping/admin_dict_mapping.jsp

@@ -0,0 +1,156 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title><la:message key="labels.admin_brand_title" /> | <la:message
+		key="labels.dict_mapping_configuration" /></title>
+<jsp:include page="/WEB-INF/view/common/admin/head.jsp"></jsp:include>
+</head>
+<body class="skin-blue sidebar-mini">
+	<div class="wrapper">
+		<jsp:include page="/WEB-INF/view/common/admin/header.jsp"></jsp:include>
+		<jsp:include page="/WEB-INF/view/common/admin/sidebar.jsp">
+			<jsp:param name="menuCategoryType" value="system" />
+			<jsp:param name="menuType" value="dict" />
+		</jsp:include>
+		<div class="content-wrapper">
+			<section class="content-header">
+				<h1>
+					<la:message key="labels.dict_mapping_title" />
+				</h1>
+				<ol class="breadcrumb">
+					<li><la:link href="list">
+							<la:message key="labels.dict_list_link" />
+						</la:link></li>
+					<li><la:message key="labels.dict_mapping_list_link" /></li>
+				</ol>
+			</section>
+			<section class="content">
+				<div class="row">
+					<div class="col-md-12">
+						<div class="box box-primary">
+							<div class="box-header with-border">
+								<h3 class="box-title">
+									<la:message key="labels.dict_mapping_list_link" />
+								</h3>
+								<div class="btn-group pull-right">
+									<la:link href="/admin/dict" styleClass="btn btn-default btn-xs">
+										<i class="fa fa-book"></i>
+										<la:message key="labels.dict_list_link" />
+									</la:link>
+									<la:link href="list/1?dictId=${f:u(dictId)}"
+										styleClass="btn btn-primary btn-xs">
+										<i class="fa fa-th-list"></i>
+										<la:message key="labels.dict_mapping_list_link" />
+									</la:link>
+									<la:link href="createnew/${f:u(dictId)}"
+										styleClass="btn btn-success btn-xs">
+										<i class="fa fa-plus"></i>
+										<la:message key="labels.dict_mapping_link_create" />
+									</la:link>
+									<la:link href="downloadpage/${f:u(dictId)}"
+										styleClass="btn btn-primary btn-xs">
+										<i class="fa fa-download"></i>
+										<la:message key="labels.dict_mapping_link_download" />
+									</la:link>
+									<la:link href="uploadpage/${f:u(dictId)}"
+										styleClass="btn btn-success btn-xs">
+										<i class="fa fa-upload"></i>
+										<la:message key="labels.dict_mapping_link_upload" />
+									</la:link>
+								</div>
+							</div>
+							<!-- /.box-header -->
+							<div class="box-body">
+								<%-- Message --%>
+								<div>
+									<la:info id="msg" message="true">
+										<div class="alert alert-info">${msg}</div>
+									</la:info>
+									<la:errors />
+								</div>
+								<%-- List --%>
+								<c:if test="${charMappingPager.allRecordCount == 0}">
+									<div class="row top10">
+										<div class="col-sm-12">
+											<i class="fa fa-info-circle text-light-blue"></i>
+											<la:message key="labels.list_could_not_find_crud_table" />
+										</div>
+									</div>
+								</c:if>
+								<c:if test="${charMappingPager.allRecordCount > 0}">
+									<div class="row">
+										<div class="col-sm-12">
+											<table class="table table-bordered table-striped">
+												<thead>
+													<tr>
+														<th><la:message key="labels.dict_mapping_source" /></th>
+														<th><la:message key="labels.dict_mapping_target" /></th>
+													</tr>
+												</thead>
+												<tbody>
+													<c:forEach var="data" varStatus="s"
+														items="${charMappingItemItems}">
+														<tr
+															data-href="${contextPath}/admin/dict/mapping/details/${f:u(dictId)}/4/${f:u(data.id)}">
+															<td>${f:h(data.inputs)}</td>
+															<td>${f:h(data.output)}</td>
+														</tr>
+													</c:forEach>
+												</tbody>
+											</table>
+										</div>
+									</div>
+									<c:set var="pager" value="${charMappingPager}" scope="request" />
+									<div class="row">
+										<div class="col-sm-2">
+											<la:message key="labels.pagination_page_guide_msg"
+												arg0="${f:h(pager.currentPageNumber)}"
+												arg1="${f:h(pager.allPageCount)}"
+												arg2="${f:h(pager.allRecordCount)}" />
+										</div>
+										<div class="col-sm-10">
+											<ul class="pagination pagination-sm no-margin pull-right">
+												<c:if test="${pager.existPrePage}">
+													<li class="prev"><la:link
+															href="list/${pager.currentPageNumber - 1}?dictId=${f:u(dictId)}">
+															<la:message key="labels.prev_page" />
+														</la:link></li>
+												</c:if>
+												<c:if test="${!pager.existPrePage}">
+													<li class="prev disabled"><a href="#"><la:message
+																key="labels.prev_page" /></a></li>
+												</c:if>
+												<c:forEach var="p" varStatus="s"
+													items="${pager.pageNumberList}">
+													<li
+														<c:if test="${p == pager.currentPageNumber}">class="active"</c:if>><la:link
+															href="list/${p}?dictId=${f:u(dictId)}">${p}</la:link></li>
+												</c:forEach>
+												<c:if test="${pager.existNextPage}">
+													<li class="next"><la:link
+															href="list/${pager.currentPageNumber + 1}?dictId=${f:u(dictId)}">
+															<la:message key="labels.next_page" />
+														</la:link></li>
+												</c:if>
+												<c:if test="${!pager.existNextPage}">
+													<li class="next disabled"><a href="#"><la:message
+																key="labels.next_page" /></a></li>
+												</c:if>
+											</ul>
+										</div>
+									</div>
+								</c:if>
+							</div>
+							<!-- /.box-body -->
+						</div>
+						<!-- /.box -->
+					</div>
+				</div>
+			</section>
+		</div>
+		<jsp:include page="/WEB-INF/view/common/admin/footer.jsp"></jsp:include>
+	</div>
+	<jsp:include page="/WEB-INF/view/common/admin/foot.jsp"></jsp:include>
+</body>
+</html>

+ 127 - 0
src/main/webapp/WEB-INF/view/admin/dict/mapping/admin_dict_mapping_details.jsp

@@ -0,0 +1,127 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title><la:message key="labels.admin_brand_title" /> | <la:message
+		key="labels.dict_mapping_configuration" /></title>
+<jsp:include page="/WEB-INF/view/common/admin/head.jsp"></jsp:include>
+</head>
+<body class="skin-blue sidebar-mini">
+	<div class="wrapper">
+		<jsp:include page="/WEB-INF/view/common/admin/header.jsp"></jsp:include>
+		<jsp:include page="/WEB-INF/view/common/admin/sidebar.jsp">
+			<jsp:param name="menuCategoryType" value="system" />
+			<jsp:param name="menuType" value="dict" />
+		</jsp:include>
+		<div class="content-wrapper">
+			<section class="content-header">
+				<h1>
+					<la:message key="labels.dict_mapping_title" />
+				</h1>
+				<ol class="breadcrumb">
+					<li><la:link href="list">
+							<la:message key="labels.dict_list_link" />
+						</la:link></li>
+					<li><la:link href="list/0/?dictId=${f:u(dictId)}">
+							<la:message key="labels.dict_mapping_list_link" />
+						</la:link></li>
+					<li class="active"><la:message
+							key="labels.dict_mapping_link_details" /></li>
+				</ol>
+			</section>
+			<section class="content">
+				<la:form action="/admin/dict/mapping/">
+					<la:hidden property="crudMode" />
+					<la:hidden property="dictId" />
+					<c:if test="${crudMode==2 || crudMode==3 || crudMode==4}">
+						<la:hidden property="id" />
+					</c:if>
+					<div class="row">
+						<div class="col-md-12">
+							<div
+								class="box <c:if test="${crudMode == 1}">box-success</c:if><c:if test="${crudMode == 2}">box-warning</c:if><c:if test="${crudMode == 3}">box-danger</c:if><c:if test="${crudMode == 4}">box-primary</c:if>">
+								<%-- Box Header --%>
+								<div class="box-header with-border">
+									<h3 class="box-title">
+										<c:if test="${crudMode == 1}">
+											<la:message key="labels.dict_mapping_link_create" />
+										</c:if>
+										<c:if test="${crudMode == 2}">
+											<la:message key="labels.dict_mapping_link_edit" />
+										</c:if>
+										<c:if test="${crudMode == 3}">
+											<la:message key="labels.dict_mapping_link_delete" />
+										</c:if>
+										<c:if test="${crudMode == 4}">
+											<la:message key="labels.dict_mapping_link_details" />
+										</c:if>
+									</h3>
+									<div class="btn-group pull-right">
+										<la:link href="/admin/dict"
+											styleClass="btn btn-default btn-xs">
+											<i class="fa fa-book"></i>
+											<la:message key="labels.dict_list_link" />
+										</la:link>
+										<la:link href="../list/1?dictId=${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-th-list"></i>
+											<la:message key="labels.dict_mapping_list_link" />
+										</la:link>
+										<la:link href="../createnew/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-plus"></i>
+											<la:message key="labels.dict_mapping_link_create" />
+										</la:link>
+										<la:link href="../downloadpage/${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-download"></i>
+											<la:message key="labels.dict_mapping_link_download" />
+										</la:link>
+										<la:link href="../uploadpage/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-upload"></i>
+											<la:message key="labels.dict_mapping_link_upload" />
+										</la:link>
+									</div>
+								</div>
+								<%-- Box Body --%>
+								<div class="box-body">
+									<%-- Message --%>
+									<div>
+										<la:info id="msg" message="true">
+											<div class="alert alert-info">${msg}</div>
+										</la:info>
+										<la:errors />
+									</div>
+									<%-- Form Fields --%>
+									<table class="table table-bordered">
+										<tbody>
+											<tr>
+												<th><la:message
+														key="labels.dict_mapping_source" /></th>
+												<td>${f:br(f:h(inputs))}<la:hidden property="inputs" /></td>
+											</tr>
+											<tr>
+												<th><la:message key="labels.dict_mapping_target" /></th>
+												<td>${f:br(f:h(output))}<la:hidden property="output" /></td>
+											</tr>
+										</tbody>
+									</table>
+								</div>
+								<!-- /.box-body -->
+								<div class="box-footer">
+									<jsp:include page="/WEB-INF/view/common/admin/crud/buttons.jsp"></jsp:include>
+								</div>
+								<!-- /.box-footer -->
+							</div>
+							<!-- /.box -->
+						</div>
+					</div>
+				</la:form>
+			</section>
+		</div>
+		<jsp:include page="/WEB-INF/view/common/admin/footer.jsp"></jsp:include>
+	</div>
+	<jsp:include page="/WEB-INF/view/common/admin/foot.jsp"></jsp:include>
+</body>
+</html>

+ 103 - 0
src/main/webapp/WEB-INF/view/admin/dict/mapping/admin_dict_mapping_download.jsp

@@ -0,0 +1,103 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title><la:message key="labels.admin_brand_title" /> | <la:message
+		key="labels.dict_mapping_configuration" /></title>
+<jsp:include page="/WEB-INF/view/common/admin/head.jsp"></jsp:include>
+</head>
+<body class="skin-blue sidebar-mini">
+	<div class="wrapper">
+		<jsp:include page="/WEB-INF/view/common/admin/header.jsp"></jsp:include>
+		<jsp:include page="/WEB-INF/view/common/admin/sidebar.jsp">
+			<jsp:param name="menuCategoryType" value="system" />
+			<jsp:param name="menuType" value="dict" />
+		</jsp:include>
+		<div class="content-wrapper">
+			<section class="content-header">
+				<h1>
+					<la:message key="labels.dict_mapping_title" />
+				</h1>
+				<ol class="breadcrumb">
+					<li><la:link href="list">
+							<la:message key="labels.dict_list_link" />
+						</la:link></li>
+					<li><la:link href="list/0/?dictId=${f:u(dictId)}">
+							<la:message key="labels.dict_mapping_list_link" />
+						</la:link></li>
+					<li class="active"><la:message
+							key="labels.dict_mapping_link_download" /></li>
+				</ol>
+			</section>
+			<section class="content">
+				<la:form action="/admin/dict/mapping/">
+					<la:hidden property="dictId" />
+					<div class="row">
+						<div class="col-md-12">
+							<div class="box box-primary">
+								<div class="box-header with-border">
+									<h3 class="box-title">
+										<la:message key="labels.dict_mapping_link_download" />
+									</h3>
+									<div class="btn-group pull-right">
+										<la:link href="/admin/dict"
+											styleClass="btn btn-default btn-xs">
+											<i class="fa fa-book"></i>
+											<la:message key="labels.dict_list_link" />
+										</la:link>
+										<la:link href="../list/0/?dictId=${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-th-list"></i>
+											<la:message key="labels.dict_mapping_list_link" />
+										</la:link>
+										<la:link href="../createnew/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-plus"></i>
+											<la:message key="labels.dict_mapping_link_create" />
+										</la:link>
+										<la:link href="../downloadpage/${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-download"></i>
+											<la:message key="labels.dict_mapping_link_download" />
+										</la:link>
+										<la:link href="../uploadpage/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-upload"></i>
+											<la:message key="labels.dict_mapping_link_upload" />
+										</la:link>
+									</div>
+								</div>
+								<!-- /.box-header -->
+								<div class="box-body">
+									<%-- Message --%>
+									<div>
+										<la:info id="msg" message="true">
+											<div class="alert alert-info">${msg}</div>
+										</la:info>
+										<la:errors />
+									</div>
+									<div class="form-group">
+										<label for="name" class="col-sm-12 control-label">${f:h(path)}</label>
+									</div>
+								</div>
+								<!-- /.box-body -->
+								<div class="box-footer">
+									<button type="submit" class="btn btn-primary" name="download"
+										value="<la:message key="labels.dict_mapping_button_download" />">
+										<i class="fa fa-download"></i>
+										<la:message key="labels.dict_mapping_button_download" />
+									</button>
+								</div>
+								<!-- /.box-footer -->
+							</div>
+							<!-- /.box -->
+						</div>
+					</div>
+				</la:form>
+			</section>
+		</div>
+		<jsp:include page="/WEB-INF/view/common/admin/footer.jsp"></jsp:include>
+	</div>
+	<jsp:include page="/WEB-INF/view/common/admin/foot.jsp"></jsp:include>
+</body>
+</html>

+ 129 - 0
src/main/webapp/WEB-INF/view/admin/dict/mapping/admin_dict_mapping_edit.jsp

@@ -0,0 +1,129 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title><la:message key="labels.admin_brand_title" /> | <la:message
+		key="labels.dict_mapping_configuration" /></title>
+<jsp:include page="/WEB-INF/view/common/admin/head.jsp"></jsp:include>
+</head>
+<body class="skin-blue sidebar-mini">
+	<div class="wrapper">
+		<jsp:include page="/WEB-INF/view/common/admin/header.jsp"></jsp:include>
+		<jsp:include page="/WEB-INF/view/common/admin/sidebar.jsp">
+			<jsp:param name="menuCategoryType" value="system" />
+			<jsp:param name="menuType" value="dict" />
+		</jsp:include>
+		<div class="content-wrapper">
+			<section class="content-header">
+				<h1>
+					<la:message key="labels.dict_mapping_title" />
+				</h1>
+				<ol class="breadcrumb">
+					<li><la:link href="list">
+							<la:message key="labels.dict_list_link" />
+						</la:link></li>
+					<li><la:link href="list/0/?dictId=${f:u(dictId)}">
+							<la:message key="labels.dict_mapping_list_link" />
+						</la:link></li>
+					<c:if test="${crudMode == 1}">
+						<li class="active"><la:message
+								key="labels.dict_mapping_link_create" /></li>
+					</c:if>
+					<c:if test="${crudMode == 2}">
+						<li class="active"><la:message
+								key="labels.dict_mapping_link_edit" /></li>
+					</c:if>
+				</ol>
+			</section>
+			<section class="content">
+				<la:form action="/admin/dict/mapping/" styleClass="form-horizontal">
+					<la:hidden property="crudMode" />
+					<la:hidden property="dictId" />
+					<c:if test="${crudMode==2}">
+						<la:hidden property="id" />
+					</c:if>
+					<div class="row">
+						<div class="col-md-12">
+							<div
+								class="box <c:if test="${crudMode == 1}">box-success</c:if><c:if test="${crudMode == 2}">box-warning</c:if>">
+								<div class="box-header with-border">
+									<h3 class="box-title">
+										<c:if test="${crudMode == 1}">
+											<la:message key="labels.dict_mapping_link_create" />
+										</c:if>
+										<c:if test="${crudMode == 2}">
+											<la:message key="labels.dict_mapping_link_edit" />
+										</c:if>
+									</h3>
+									<div class="btn-group pull-right">
+										<la:link href="/admin/dict"
+											styleClass="btn btn-default btn-xs">
+											<i class="fa fa-book"></i>
+											<la:message key="labels.dict_list_link" />
+										</la:link>
+										<la:link href="../list/1?dictId=${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-th-list"></i>
+											<la:message key="labels.dict_mapping_list_link" />
+										</la:link>
+										<la:link href="../createnew/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-plus"></i>
+											<la:message key="labels.dict_mapping_link_create" />
+										</la:link>
+										<la:link href="../downloadpage/${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-download"></i>
+											<la:message key="labels.dict_mapping_link_download" />
+										</la:link>
+										<la:link href="../uploadpage/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-upload"></i>
+											<la:message key="labels.dict_mapping_link_upload" />
+										</la:link>
+									</div>
+								</div>
+								<!-- /.box-header -->
+								<div class="box-body">
+									<div>
+										<la:info id="msg" message="true">
+											<div class="alert alert-info">${msg}</div>
+										</la:info>
+										<la:errors property="_global" />
+									</div>
+									<div class="form-group">
+										<label for="term" class="col-sm-3 control-label"><la:message
+												key="labels.dict_mapping_source" /></label>
+										<div class="col-sm-9">
+											<la:errors property="inputs" />
+											<la:textarea property="inputs" rows="5"
+												styleClass="form-control" />
+										</div>
+									</div>
+									<div class="form-group">
+										<label for="output" class="col-sm-3 control-label"><la:message
+												key="labels.dict_mapping_target" /></label>
+										<div class="col-sm-9">
+											<la:errors property="output" />
+											<la:textarea property="output" rows="5"
+												styleClass="form-control" />
+										</div>
+									</div>
+								</div>
+								<!-- /.box-body -->
+								<div class="box-footer">
+									<jsp:include page="/WEB-INF/view/common/admin/crud/buttons.jsp"></jsp:include>
+								</div>
+								<!-- /.box-footer -->
+							</div>
+							<!-- /.box -->
+						</div>
+					</div>
+				</la:form>
+			</section>
+		</div>
+		<jsp:include page="/WEB-INF/view/common/admin/footer.jsp"></jsp:include>
+	</div>
+	<jsp:include page="/WEB-INF/view/common/admin/foot.jsp"></jsp:include>
+</body>
+</html>

+ 107 - 0
src/main/webapp/WEB-INF/view/admin/dict/mapping/admin_dict_mapping_upload.jsp

@@ -0,0 +1,107 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title><la:message key="labels.admin_brand_title" /> | <la:message
+		key="labels.dict_mapping_configuration" /></title>
+<jsp:include page="/WEB-INF/view/common/admin/head.jsp"></jsp:include>
+</head>
+<body class="skin-blue sidebar-mini">
+	<div class="wrapper">
+		<jsp:include page="/WEB-INF/view/common/admin/header.jsp"></jsp:include>
+		<jsp:include page="/WEB-INF/view/common/admin/sidebar.jsp">
+			<jsp:param name="menuCategoryType" value="system" />
+			<jsp:param name="menuType" value="dict" />
+		</jsp:include>
+		<div class="content-wrapper">
+			<section class="content-header">
+				<h1>
+					<la:message key="labels.dict_mapping_title" />
+				</h1>
+				<ol class="breadcrumb">
+					<li><la:link href="list">
+							<la:message key="labels.dict_list_link" />
+						</la:link></li>
+					<li><la:link href="list/0/?dictId=${f:u(dictId)}">
+							<la:message key="labels.dict_mapping_list_link" />
+						</la:link></li>
+					<li class="active"><la:message
+							key="labels.dict_mapping_link_upload" /></li>
+				</ol>
+			</section>
+			<section class="content">
+				<la:form action="/admin/dict/mapping/upload" enctype="multipart/form-data">
+					<la:hidden property="dictId" />
+					<div class="row">
+						<div class="col-md-12">
+							<div class="box box-primary">
+								<div class="box-header with-border">
+									<h3 class="box-title">
+										<la:message key="labels.dict_mapping_link_upload" />
+									</h3>
+									<div class="btn-group pull-right">
+										<la:link href="/admin/dict"
+											styleClass="btn btn-default btn-xs">
+											<i class="fa fa-book"></i>
+											<la:message key="labels.dict_list_link" />
+										</la:link>
+										<la:link href="../list/0/?dictId=${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-th-list"></i>
+											<la:message key="labels.dict_mapping_list_link" />
+										</la:link>
+										<la:link href="../createnew/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-plus"></i>
+											<la:message key="labels.dict_mapping_link_create" />
+										</la:link>
+										<la:link href="../downloadpage/${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-download"></i>
+											<la:message key="labels.dict_mapping_link_download" />
+										</la:link>
+										<la:link href="../uploadpage/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-upload"></i>
+											<la:message key="labels.dict_mapping_link_upload" />
+										</la:link>
+									</div>
+								</div>
+								<!-- /.box-header -->
+								<div class="box-body">
+									<%-- Message --%>
+									<div>
+										<la:info id="msg" message="true">
+											<div class="alert alert-info">${msg}</div>
+										</la:info>
+										<la:errors />
+									</div>
+									<div class="form-group">
+										<label for="name" class="col-sm-3 control-label"><la:message
+												key="labels.dict_mapping_file" /></label>
+										<div class="col-sm-9">
+											<input type="file" name="mappingFile" />
+										</div>
+									</div>
+								</div>
+								<!-- /.box-body -->
+								<div class="box-footer">
+									<button type="submit" class="btn btn-success"
+										value="<la:message key="labels.dict_mapping_button_upload" />">
+										<i class="fa fa-upload"></i>
+										<la:message key="labels.dict_mapping_button_upload" />
+									</button>
+								</div>
+								<!-- /.box-footer -->
+							</div>
+							<!-- /.box -->
+						</div>
+					</div>
+				</la:form>
+			</section>
+		</div>
+		<jsp:include page="/WEB-INF/view/common/admin/footer.jsp"></jsp:include>
+	</div>
+	<jsp:include page="/WEB-INF/view/common/admin/foot.jsp"></jsp:include>
+</body>
+</html>

+ 154 - 0
src/main/webapp/WEB-INF/view/admin/dict/protwords/admin_dict_protwords.jsp

@@ -0,0 +1,154 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title><la:message key="labels.admin_brand_title" /> | <la:message
+		key="labels.dict_protwords_configuration" /></title>
+<jsp:include page="/WEB-INF/view/common/admin/head.jsp"></jsp:include>
+</head>
+<body class="skin-blue sidebar-mini">
+	<div class="wrapper">
+		<jsp:include page="/WEB-INF/view/common/admin/header.jsp"></jsp:include>
+		<jsp:include page="/WEB-INF/view/common/admin/sidebar.jsp">
+			<jsp:param name="menuCategoryType" value="system" />
+			<jsp:param name="menuType" value="dict" />
+		</jsp:include>
+		<div class="content-wrapper">
+			<section class="content-header">
+				<h1>
+					<la:message key="labels.dict_protwords_title" />
+				</h1>
+				<ol class="breadcrumb">
+					<li><la:link href="list">
+							<la:message key="labels.dict_list_link" />
+						</la:link></li>
+					<li><la:message key="labels.dict_protwords_list_link" /></li>
+				</ol>
+			</section>
+			<section class="content">
+				<div class="row">
+					<div class="col-md-12">
+						<div class="box box-primary">
+							<div class="box-header with-border">
+								<h3 class="box-title">
+									<la:message key="labels.dict_protwords_list_link" />
+								</h3>
+								<div class="btn-group pull-right">
+									<la:link href="/admin/dict" styleClass="btn btn-default btn-xs">
+										<i class="fa fa-book"></i>
+										<la:message key="labels.dict_list_link" />
+									</la:link>
+									<la:link href="list/1?dictId=${f:u(dictId)}"
+										styleClass="btn btn-primary btn-xs">
+										<i class="fa fa-th-list"></i>
+										<la:message key="labels.dict_protwords_list_link" />
+									</la:link>
+									<la:link href="createnew/${f:u(dictId)}"
+										styleClass="btn btn-success btn-xs">
+										<i class="fa fa-plus"></i>
+										<la:message key="labels.dict_protwords_link_create" />
+									</la:link>
+									<la:link href="downloadpage/${f:u(dictId)}"
+										styleClass="btn btn-primary btn-xs">
+										<i class="fa fa-download"></i>
+										<la:message key="labels.dict_protwords_link_download" />
+									</la:link>
+									<la:link href="uploadpage/${f:u(dictId)}"
+										styleClass="btn btn-success btn-xs">
+										<i class="fa fa-upload"></i>
+										<la:message key="labels.dict_protwords_link_upload" />
+									</la:link>
+								</div>
+							</div>
+							<!-- /.box-header -->
+							<div class="box-body">
+								<%-- Message --%>
+								<div>
+									<la:info id="msg" message="true">
+										<div class="alert alert-info">${msg}</div>
+									</la:info>
+									<la:errors />
+								</div>
+								<%-- List --%>
+								<c:if test="${protwordsPager.allRecordCount == 0}">
+									<div class="row top10">
+										<div class="col-sm-12">
+											<i class="fa fa-info-circle text-light-blue"></i>
+											<la:message key="labels.list_could_not_find_crud_table" />
+										</div>
+									</div>
+								</c:if>
+								<c:if test="${protwordsPager.allRecordCount > 0}">
+									<div class="row">
+										<div class="col-sm-12">
+											<table class="table table-bordered table-striped">
+												<thead>
+													<tr>
+														<th><la:message key="labels.dict_protwords_source" /></th>
+													</tr>
+												</thead>
+												<tbody>
+													<c:forEach var="data" varStatus="s"
+														items="${protwordsItemItems}">
+														<tr
+															data-href="${contextPath}/admin/dict/protwords/details/${f:u(dictId)}/4/${f:u(data.id)}">
+															<td>${f:h(data.inputValue)}</td>
+														</tr>
+													</c:forEach>
+												</tbody>
+											</table>
+										</div>
+									</div>
+									<c:set var="pager" value="${protwordsPager}" scope="request" />
+									<div class="row">
+										<div class="col-sm-2">
+											<la:message key="labels.pagination_page_guide_msg"
+												arg0="${f:h(pager.currentPageNumber)}"
+												arg1="${f:h(pager.allPageCount)}"
+												arg2="${f:h(pager.allRecordCount)}" />
+										</div>
+										<div class="col-sm-10">
+											<ul class="pagination pagination-sm no-margin pull-right">
+												<c:if test="${pager.existPrePage}">
+													<li class="prev"><la:link
+															href="list/${pager.currentPageNumber - 1}?dictId=${f:u(dictId)}">
+															<la:message key="labels.prev_page" />
+														</la:link></li>
+												</c:if>
+												<c:if test="${!pager.existPrePage}">
+													<li class="prev disabled"><a href="#"><la:message
+																key="labels.prev_page" /></a></li>
+												</c:if>
+												<c:forEach var="p" varStatus="s"
+													items="${pager.pageNumberList}">
+													<li
+														<c:if test="${p == pager.currentPageNumber}">class="active"</c:if>><la:link
+															href="list/${p}?dictId=${f:u(dictId)}">${p}</la:link></li>
+												</c:forEach>
+												<c:if test="${pager.existNextPage}">
+													<li class="next"><la:link
+															href="list/${pager.currentPageNumber + 1}?dictId=${f:u(dictId)}">
+															<la:message key="labels.next_page" />
+														</la:link></li>
+												</c:if>
+												<c:if test="${!pager.existNextPage}">
+													<li class="next disabled"><a href="#"><la:message
+																key="labels.next_page" /></a></li>
+												</c:if>
+											</ul>
+										</div>
+									</div>
+								</c:if>
+							</div>
+							<!-- /.box-body -->
+						</div>
+						<!-- /.box -->
+					</div>
+				</div>
+			</section>
+		</div>
+		<jsp:include page="/WEB-INF/view/common/admin/footer.jsp"></jsp:include>
+	</div>
+	<jsp:include page="/WEB-INF/view/common/admin/foot.jsp"></jsp:include>
+</body>
+</html>

+ 123 - 0
src/main/webapp/WEB-INF/view/admin/dict/protwords/admin_dict_protwords_details.jsp

@@ -0,0 +1,123 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title><la:message key="labels.admin_brand_title" /> | <la:message
+		key="labels.dict_protwords_configuration" /></title>
+<jsp:include page="/WEB-INF/view/common/admin/head.jsp"></jsp:include>
+</head>
+<body class="skin-blue sidebar-mini">
+	<div class="wrapper">
+		<jsp:include page="/WEB-INF/view/common/admin/header.jsp"></jsp:include>
+		<jsp:include page="/WEB-INF/view/common/admin/sidebar.jsp">
+			<jsp:param name="menuCategoryType" value="system" />
+			<jsp:param name="menuType" value="dict" />
+		</jsp:include>
+		<div class="content-wrapper">
+			<section class="content-header">
+				<h1>
+					<la:message key="labels.dict_protwords_title" />
+				</h1>
+				<ol class="breadcrumb">
+					<li><la:link href="list">
+							<la:message key="labels.dict_list_link" />
+						</la:link></li>
+					<li><la:link href="list/0/?dictId=${f:u(dictId)}">
+							<la:message key="labels.dict_protwords_list_link" />
+						</la:link></li>
+					<li class="active"><la:message
+							key="labels.dict_protwords_link_details" /></li>
+				</ol>
+			</section>
+			<section class="content">
+				<la:form action="/admin/dict/protwords/">
+					<la:hidden property="crudMode" />
+					<la:hidden property="dictId" />
+					<c:if test="${crudMode==2 || crudMode==3 || crudMode==4}">
+						<la:hidden property="id" />
+					</c:if>
+					<div class="row">
+						<div class="col-md-12">
+							<div
+								class="box <c:if test="${crudMode == 1}">box-success</c:if><c:if test="${crudMode == 2}">box-warning</c:if><c:if test="${crudMode == 3}">box-danger</c:if><c:if test="${crudMode == 4}">box-primary</c:if>">
+								<%-- Box Header --%>
+								<div class="box-header with-border">
+									<h3 class="box-title">
+										<c:if test="${crudMode == 1}">
+											<la:message key="labels.dict_protwords_link_create" />
+										</c:if>
+										<c:if test="${crudMode == 2}">
+											<la:message key="labels.dict_protwords_link_edit" />
+										</c:if>
+										<c:if test="${crudMode == 3}">
+											<la:message key="labels.dict_protwords_link_delete" />
+										</c:if>
+										<c:if test="${crudMode == 4}">
+											<la:message key="labels.dict_protwords_link_details" />
+										</c:if>
+									</h3>
+									<div class="btn-group pull-right">
+										<la:link href="/admin/dict"
+											styleClass="btn btn-default btn-xs">
+											<i class="fa fa-book"></i>
+											<la:message key="labels.dict_list_link" />
+										</la:link>
+										<la:link href="../list/1?dictId=${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-th-list"></i>
+											<la:message key="labels.dict_protwords_list_link" />
+										</la:link>
+										<la:link href="../createnew/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-plus"></i>
+											<la:message key="labels.dict_protwords_link_create" />
+										</la:link>
+										<la:link href="../downloadpage/${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-download"></i>
+											<la:message key="labels.dict_protwords_link_download" />
+										</la:link>
+										<la:link href="../uploadpage/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-upload"></i>
+											<la:message key="labels.dict_protwords_link_upload" />
+										</la:link>
+									</div>
+								</div>
+								<%-- Box Body --%>
+								<div class="box-body">
+									<%-- Message --%>
+									<div>
+										<la:info id="msg" message="true">
+											<div class="alert alert-info">${msg}</div>
+										</la:info>
+										<la:errors />
+									</div>
+									<%-- Form Fields --%>
+									<table class="table table-bordered">
+										<tbody>
+											<tr>
+												<th><la:message
+														key="labels.dict_protwords_source" /></th>
+												<td>${f:h(input)}<la:hidden property="input" /></td>
+											</tr>
+										</tbody>
+									</table>
+								</div>
+								<!-- /.box-body -->
+								<div class="box-footer">
+									<jsp:include page="/WEB-INF/view/common/admin/crud/buttons.jsp"></jsp:include>
+								</div>
+								<!-- /.box-footer -->
+							</div>
+							<!-- /.box -->
+						</div>
+					</div>
+				</la:form>
+			</section>
+		</div>
+		<jsp:include page="/WEB-INF/view/common/admin/footer.jsp"></jsp:include>
+	</div>
+	<jsp:include page="/WEB-INF/view/common/admin/foot.jsp"></jsp:include>
+</body>
+</html>

+ 103 - 0
src/main/webapp/WEB-INF/view/admin/dict/protwords/admin_dict_protwords_download.jsp

@@ -0,0 +1,103 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title><la:message key="labels.admin_brand_title" /> | <la:message
+		key="labels.dict_protwords_configuration" /></title>
+<jsp:include page="/WEB-INF/view/common/admin/head.jsp"></jsp:include>
+</head>
+<body class="skin-blue sidebar-mini">
+	<div class="wrapper">
+		<jsp:include page="/WEB-INF/view/common/admin/header.jsp"></jsp:include>
+		<jsp:include page="/WEB-INF/view/common/admin/sidebar.jsp">
+			<jsp:param name="menuCategoryType" value="system" />
+			<jsp:param name="menuType" value="dict" />
+		</jsp:include>
+		<div class="content-wrapper">
+			<section class="content-header">
+				<h1>
+					<la:message key="labels.dict_protwords_title" />
+				</h1>
+				<ol class="breadcrumb">
+					<li><la:link href="list">
+							<la:message key="labels.dict_list_link" />
+						</la:link></li>
+					<li><la:link href="list/0/?dictId=${f:u(dictId)}">
+							<la:message key="labels.dict_protwords_list_link" />
+						</la:link></li>
+					<li class="active"><la:message
+							key="labels.dict_protwords_link_download" /></li>
+				</ol>
+			</section>
+			<section class="content">
+				<la:form action="/admin/dict/protwords/">
+					<la:hidden property="dictId" />
+					<div class="row">
+						<div class="col-md-12">
+							<div class="box box-primary">
+								<div class="box-header with-border">
+									<h3 class="box-title">
+										<la:message key="labels.dict_protwords_link_download" />
+									</h3>
+									<div class="btn-group pull-right">
+										<la:link href="/admin/dict"
+											styleClass="btn btn-default btn-xs">
+											<i class="fa fa-book"></i>
+											<la:message key="labels.dict_list_link" />
+										</la:link>
+										<la:link href="../list/0/?dictId=${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-th-list"></i>
+											<la:message key="labels.dict_protwords_list_link" />
+										</la:link>
+										<la:link href="../createnew/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-plus"></i>
+											<la:message key="labels.dict_protwords_link_create" />
+										</la:link>
+										<la:link href="../downloadpage/${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-download"></i>
+											<la:message key="labels.dict_protwords_link_download" />
+										</la:link>
+										<la:link href="../uploadpage/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-upload"></i>
+											<la:message key="labels.dict_protwords_link_upload" />
+										</la:link>
+									</div>
+								</div>
+								<!-- /.box-header -->
+								<div class="box-body">
+									<%-- Message --%>
+									<div>
+										<la:info id="msg" message="true">
+											<div class="alert alert-info">${msg}</div>
+										</la:info>
+										<la:errors />
+									</div>
+									<div class="form-group">
+										<label for="name" class="col-sm-12 control-label">${f:h(path)}</label>
+									</div>
+								</div>
+								<!-- /.box-body -->
+								<div class="box-footer">
+									<button type="submit" class="btn btn-primary" name="download"
+										value="<la:message key="labels.dict_protwords_button_download" />">
+										<i class="fa fa-download"></i>
+										<la:message key="labels.dict_protwords_button_download" />
+									</button>
+								</div>
+								<!-- /.box-footer -->
+							</div>
+							<!-- /.box -->
+						</div>
+					</div>
+				</la:form>
+			</section>
+		</div>
+		<jsp:include page="/WEB-INF/view/common/admin/footer.jsp"></jsp:include>
+	</div>
+	<jsp:include page="/WEB-INF/view/common/admin/foot.jsp"></jsp:include>
+</body>
+</html>

+ 120 - 0
src/main/webapp/WEB-INF/view/admin/dict/protwords/admin_dict_protwords_edit.jsp

@@ -0,0 +1,120 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title><la:message key="labels.admin_brand_title" /> | <la:message
+		key="labels.dict_protwords_configuration" /></title>
+<jsp:include page="/WEB-INF/view/common/admin/head.jsp"></jsp:include>
+</head>
+<body class="skin-blue sidebar-mini">
+	<div class="wrapper">
+		<jsp:include page="/WEB-INF/view/common/admin/header.jsp"></jsp:include>
+		<jsp:include page="/WEB-INF/view/common/admin/sidebar.jsp">
+			<jsp:param name="menuCategoryType" value="system" />
+			<jsp:param name="menuType" value="dict" />
+		</jsp:include>
+		<div class="content-wrapper">
+			<section class="content-header">
+				<h1>
+					<la:message key="labels.dict_protwords_title" />
+				</h1>
+				<ol class="breadcrumb">
+					<li><la:link href="list">
+							<la:message key="labels.dict_list_link" />
+						</la:link></li>
+					<li><la:link href="list/0/?dictId=${f:u(dictId)}">
+							<la:message key="labels.dict_protwords_list_link" />
+						</la:link></li>
+					<c:if test="${crudMode == 1}">
+						<li class="active"><la:message
+								key="labels.dict_protwords_link_create" /></li>
+					</c:if>
+					<c:if test="${crudMode == 2}">
+						<li class="active"><la:message
+								key="labels.dict_protwords_link_edit" /></li>
+					</c:if>
+				</ol>
+			</section>
+			<section class="content">
+				<la:form action="/admin/dict/protwords/" styleClass="form-horizontal">
+					<la:hidden property="crudMode" />
+					<la:hidden property="dictId" />
+					<c:if test="${crudMode==2}">
+						<la:hidden property="id" />
+					</c:if>
+					<div class="row">
+						<div class="col-md-12">
+							<div
+								class="box <c:if test="${crudMode == 1}">box-success</c:if><c:if test="${crudMode == 2}">box-warning</c:if>">
+								<div class="box-header with-border">
+									<h3 class="box-title">
+										<c:if test="${crudMode == 1}">
+											<la:message key="labels.dict_protwords_link_create" />
+										</c:if>
+										<c:if test="${crudMode == 2}">
+											<la:message key="labels.dict_protwords_link_edit" />
+										</c:if>
+									</h3>
+									<div class="btn-group pull-right">
+										<la:link href="/admin/dict"
+											styleClass="btn btn-default btn-xs">
+											<i class="fa fa-book"></i>
+											<la:message key="labels.dict_list_link" />
+										</la:link>
+										<la:link href="../list/1?dictId=${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-th-list"></i>
+											<la:message key="labels.dict_protwords_list_link" />
+										</la:link>
+										<la:link href="../createnew/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-plus"></i>
+											<la:message key="labels.dict_protwords_link_create" />
+										</la:link>
+										<la:link href="../downloadpage/${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-download"></i>
+											<la:message key="labels.dict_protwords_link_download" />
+										</la:link>
+										<la:link href="../uploadpage/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-upload"></i>
+											<la:message key="labels.dict_protwords_link_upload" />
+										</la:link>
+									</div>
+								</div>
+								<!-- /.box-header -->
+								<div class="box-body">
+									<div>
+										<la:info id="msg" message="true">
+											<div class="alert alert-info">${msg}</div>
+										</la:info>
+										<la:errors property="_global" />
+									</div>
+									<div class="form-group">
+										<label for="term" class="col-sm-3 control-label"><la:message
+												key="labels.dict_protwords_source" /></label>
+										<div class="col-sm-9">
+											<la:errors property="input" />
+											<la:text property="input"
+												styleClass="form-control" />
+										</div>
+									</div>
+								</div>
+								<!-- /.box-body -->
+								<div class="box-footer">
+									<jsp:include page="/WEB-INF/view/common/admin/crud/buttons.jsp"></jsp:include>
+								</div>
+								<!-- /.box-footer -->
+							</div>
+							<!-- /.box -->
+						</div>
+					</div>
+				</la:form>
+			</section>
+		</div>
+		<jsp:include page="/WEB-INF/view/common/admin/footer.jsp"></jsp:include>
+	</div>
+	<jsp:include page="/WEB-INF/view/common/admin/foot.jsp"></jsp:include>
+</body>
+</html>

+ 107 - 0
src/main/webapp/WEB-INF/view/admin/dict/protwords/admin_dict_protwords_upload.jsp

@@ -0,0 +1,107 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title><la:message key="labels.admin_brand_title" /> | <la:message
+		key="labels.dict_protwords_configuration" /></title>
+<jsp:include page="/WEB-INF/view/common/admin/head.jsp"></jsp:include>
+</head>
+<body class="skin-blue sidebar-mini">
+	<div class="wrapper">
+		<jsp:include page="/WEB-INF/view/common/admin/header.jsp"></jsp:include>
+		<jsp:include page="/WEB-INF/view/common/admin/sidebar.jsp">
+			<jsp:param name="menuCategoryType" value="system" />
+			<jsp:param name="menuType" value="dict" />
+		</jsp:include>
+		<div class="content-wrapper">
+			<section class="content-header">
+				<h1>
+					<la:message key="labels.dict_protwords_title" />
+				</h1>
+				<ol class="breadcrumb">
+					<li><la:link href="list">
+							<la:message key="labels.dict_list_link" />
+						</la:link></li>
+					<li><la:link href="list/0/?dictId=${f:u(dictId)}">
+							<la:message key="labels.dict_protwords_list_link" />
+						</la:link></li>
+					<li class="active"><la:message
+							key="labels.dict_protwords_link_upload" /></li>
+				</ol>
+			</section>
+			<section class="content">
+				<la:form action="/admin/dict/protwords/upload" enctype="multipart/form-data">
+					<la:hidden property="dictId" />
+					<div class="row">
+						<div class="col-md-12">
+							<div class="box box-primary">
+								<div class="box-header with-border">
+									<h3 class="box-title">
+										<la:message key="labels.dict_protwords_link_upload" />
+									</h3>
+									<div class="btn-group pull-right">
+										<la:link href="/admin/dict"
+											styleClass="btn btn-default btn-xs">
+											<i class="fa fa-book"></i>
+											<la:message key="labels.dict_list_link" />
+										</la:link>
+										<la:link href="../list/0/?dictId=${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-th-list"></i>
+											<la:message key="labels.dict_protwords_list_link" />
+										</la:link>
+										<la:link href="../createnew/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-plus"></i>
+											<la:message key="labels.dict_protwords_link_create" />
+										</la:link>
+										<la:link href="../downloadpage/${f:u(dictId)}"
+											styleClass="btn btn-primary btn-xs">
+											<i class="fa fa-download"></i>
+											<la:message key="labels.dict_protwords_link_download" />
+										</la:link>
+										<la:link href="../uploadpage/${f:u(dictId)}"
+											styleClass="btn btn-success btn-xs">
+											<i class="fa fa-upload"></i>
+											<la:message key="labels.dict_protwords_link_upload" />
+										</la:link>
+									</div>
+								</div>
+								<!-- /.box-header -->
+								<div class="box-body">
+									<%-- Message --%>
+									<div>
+										<la:info id="msg" message="true">
+											<div class="alert alert-info">${msg}</div>
+										</la:info>
+										<la:errors />
+									</div>
+									<div class="form-group">
+										<label for="name" class="col-sm-3 control-label"><la:message
+												key="labels.dict_protwords_file" /></label>
+										<div class="col-sm-9">
+											<input type="file" name="protwordsFile" />
+										</div>
+									</div>
+								</div>
+								<!-- /.box-body -->
+								<div class="box-footer">
+									<button type="submit" class="btn btn-success"
+										value="<la:message key="labels.dict_protwords_button_upload" />">
+										<i class="fa fa-upload"></i>
+										<la:message key="labels.dict_protwords_button_upload" />
+									</button>
+								</div>
+								<!-- /.box-footer -->
+							</div>
+							<!-- /.box -->
+						</div>
+					</div>
+				</la:form>
+			</section>
+		</div>
+		<jsp:include page="/WEB-INF/view/common/admin/footer.jsp"></jsp:include>
+	</div>
+	<jsp:include page="/WEB-INF/view/common/admin/foot.jsp"></jsp:include>
+</body>
+</html>