今天我們來談談多語言。那麼它是什麼?
多語言,換句話說,
國際化,是開發可以在不改變程式邏輯的情況下適應多種語言的應用程式的一部分。考慮一下這種情況:您正在為一家大型貿易公司創建一個 Web 應用程序,該公司的用戶來自許多國家,例如使用俄語、英語和西班牙語等語言的國家。您需要為所有用戶提供方便。這個想法是這樣的:講俄語的讀者應該能夠看到俄語的數據,美國人會更舒服地閱讀英語的材料,而西班牙人則應該能夠看到西班牙語的數據(出乎意料,對吧?)今天讓我們考慮幾個國際
化
模型,對於其中之一(這是我最喜歡的:)讓我們看看 Java 中的實作。作為小白鼠,今天我們將有一個電影數據盤。我們不要太變態了,這樣發言的人就不會很多了。例如,這樣就可以了。我們將翻譯電影的名稱(導演 - 臨時演員):
1. 每種語言的翻譯表
這個模型的本質是:每種語言在資料庫中都有一個單獨的表,其中包含所有需要翻譯的單元格。這種方法的缺點是每次新增語言時都需要新增表格。也就是說,假設我們的客戶做得很好,他正在將其應用程式擴展到世界上許多國家(實際上是語言)。這意味著您需要在每個舌頭上添加一顆藥片。因此,我們將擁有一個一半或幾乎完全由輔助翻譯表組成的資料庫:
電影本身的示意圖: 翻譯表:
2. 合一
在屬於特定型號的每個表中,新增一個帶有語言板標識符的欄位。因此,資料庫也包含該表及其翻譯。問題是一個物件可以對應多種翻譯(語言)。這樣一來,就會出現實體的重複,這使得邏輯變得非常混亂和複雜化,這是不好的。
我們來看看 UML: 表電影: 表語言:
3. 每個舌頭的列
為表中每種語言的每一列建立一個單獨的翻譯列。這種方法的缺點是,同樣,如果添加大量語言,則每次都需要更改資料庫結構,這被認為是一種不好的方法。還可以想像一下要求國際化的標誌會有多誇張。可能值得考慮一個模型,其中支援的語言數量是預先已知的,數量不會太多,並且每個模型應該存在於所有語言變體中。
UML: 全包錶:
4. 外部翻譯
此選項是透過連接外部工具(Google翻譯、Bing翻譯等)來實現的。如果您需要向盡可能多的訪客提供信息,並且此類資訊有很多,則可以使用它。很多。在這種情況下,您可以決定不將所有語言的資訊直接儲存在資料庫中,而是動態翻譯它。但值得記住的是,機器翻譯的品質往往不盡人意。這個選項只能被認為是非常經濟的(當沒有資源來翻譯每個出版物時)。從正確翻譯的角度來看,一個常見的問題是,不太了解語言的翻譯人員會選擇錯誤的單字意義,使用戶感到困惑,迫使他獨立弄清楚按鈕上所寫內容的含義。同樣重要的是,不僅要正確翻譯句子,還要將其意義轉化為特定的語言和國籍。開發人員在某些語言中存在許多性別問題。他們必須根據使用者的性別複製代碼中的短語,並且還要考慮到不僅名詞有性別,而且形容詞和動詞的變形也不同。在某些情況下,在應用程式中選擇英語以外的語言後,與所選語言的單字一起,仍然存在未翻譯的元素。更糟的是,如果顯示多種語言,結果就像是一種巴比倫,一切都混在一起,用戶無法理解該應用程式。例如: https:
//cloud.google.com/translate/
5. 應用程式層級支援文件
建立單獨的檔案來儲存翻譯。可以是一舌一銼,也可以是一片一舌一銼(細粉碎)。由於許多文本可以儲存在這些文件中,這意味著表和資料庫本身不會變得臃腫,因此經常使用此選項。另一個便利是,這些欄位不需要敲資料庫,而且程式碼中的檔案可以根據請求的語言動態替換。結果,該文件對我們來說就像一本字典,其中鍵是語言,值是文字。但我們不限於下面的“.properties”格式,這些文件格式可以有很大不同 - JSON、XML 等。缺點是在這種情況下資料庫的規範化程度大大降低。而且,資料完整性不再只依賴資料庫,還依賴序列化機制。
關於此主題的優秀文章 帶有翻譯的字典文件範例:
6.各表的輔助翻譯表
我認為這是最靈活的解決方案。這種方法的本質是為語言建立一個單獨的表。當需要對相關表實現翻譯的可能性時,會建立與語言表的鏈接,並且連結表包含語言 id、元素 id 和帶有翻譯的列。它並不像聽起來那麼可怕。這種方法允許支援的語言相當靈活的可擴展性。讓我們仔細看看。
UML: 電影表: 語言表: 翻譯表: 而且,正如我上面所說,讓我們看看 Java 程式碼中的一個選項的實作(如您所知,這將是最後一個選項)。應用程式本身沒有類似的東西:我們將從控制器轉到 dao 層。我們將看看 create 方法 - 作為一個例子,這就足夠了。所以我們走吧))我們的本質是一部電影:
@Builder
@Getter
public class Movie {
private Long id;
private String producer;
}
沒什麼有趣的,只是實現第一個表的模型。
具有 dto(資料傳輸物件)轉換器的控制器:
@RestController
@RequiredArgsConstructor
@RequestMapping(path = "/cities")
public class MovieController {
private final MovieService movieService;
@PostMapping
public ResponseEntity<moviedto> create(MovieDTO movieDTO) {
return new ResponseEntity<>(toDTO(movieService.create(fromDTO(movieDTO), movieDTO.getNameTranslations()), movieDTO.getNameTranslations()), HttpStatus.CREATED);
}
private Movie fromDTO(MovieDTO dto) {
return Movie.builder()
.id(dto.getId())
.producer(dto.getProducer())
.build();
}
private MovieDTO toDTO(Movie movie, Map<string, string=""> nameTranslation) {
return MovieDTO.builder()
.id(movie.getId())
.producer(movie.getProducer())
.nameTranslations(nameTranslation)
.build();
}
}
在 DTO 中,我們將翻譯作為地圖傳遞,鍵是語言縮寫,值是翻譯值(電影名稱)。
資料傳輸物件:
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class MovieDTO {
@JsonProperty("id")
private Long id;
@JsonProperty("name")
private String producer;
@JsonProperty("nameTranslations")
private Map<String, String> nameTranslations;
}
這裡我們看到 dto 類別本身,如上面所寫,用於翻譯的映射,其餘字段是 Movie 模型的顯示。
讓我們繼續討論電影服務:
public interface MovieService {
Movie create(Movie movie, Map nameList);
}
其實現:
@Service
@RequiredArgsConstructor
public class MovieServiceImpl implements MovieService {
private final MovieDAO movieDAO;
private LanguageService languageService;
@Override
public Movie create(Movie movie, Map<string, string=""> nameList) {
movieDAO.create(movie);
Map<Long, String> map = new HashMap<>();
nameList.forEach((x, y) -> map.put(languageService.getIdByLangCode(x), y));
movieDAO.createTranslator(movie.getId(), map);
return movie;
}
}
這裡我們看到使用相對第三方的 LanguageService 服務透過縮寫來擷取語言 id。透過這個標識符,我們將翻譯(也以映射的形式)保存到連接表中。
讓我們看看 DAO:
public interface MovieDAO {
void create(Movie movie);
void createTranslator(Long movieId, Map<Long,String> nameTranslations);
}
執行:
@RequiredArgsConstructor
@Repository
public class MovieDAOImpl implements MovieDAO {
private final JdbcTemplate jdbcTemplate;
private static final String CREATE_MOVIE = "INSERT INTO movies(id, producer) VALUES(?, ?)";
private static final String CREATE_TRANSLATOR = "INSERT INTO movies_translator(movies_id, language_id, name) VALUES(?, ?, ?)";
@Override
public void create(Movie movie) {
jdbcTemplate.update(CREATE_MOVIE, movie.getId(), movie.getProducer());
}
@Override
public void createTranslator(Long movieId, Map<Long, String> nameTranslations) {
nameTranslations.forEach((x, y) -> jdbcTemplate.update(CREATE_TRANSLATOR, movieId, x, y));
}
}
在這裡我們看到了它的本質和語言的保存(字典)。是的,這裡使用了 Spring JDBC:我認為它更適合初學者,因為它更透明。讓我們繼續討論「第三方」服務。
語言服務:
public interface LanguageService {
Long getIdByLangCode(String lang);
}
執行:
@Service
@RequiredArgsConstructor
public class LanguageServiceImpl implements LanguageService {
private final LanguageDAO languageDAO;
@Override
public Long getIdByLangCode(String lang) {
return languageDAO.getIdByLangCode(lang);
}
}
沒什麼特別的,按縮寫名稱搜尋。
道:
public interface LanguageDAO {
Long getIdByLangCode(String lang);
}
執行:
@RequiredArgsConstructor
@Repository
public class LanguageDAOImpl implements LanguageDAO {
private final JdbcTemplate jdbcTemplate;
private static final String FIND_ID_BY_LANG_CODE = "SELECT id FROM languages WHERE lang_code = ?";
@Override
public Long getIdByLangCode(String lang) {
return jdbcTemplate.queryForObject(FIND_ID_BY_LANG_CODE, Long.class, lang);
}
}
結構: 以上描述的所有模型都有生命權。您需要根據情況決定使用哪一種。當然,這還不是全部:還有很多不同的方法,包括針對不同的語言使用不同的資料庫、使用快取、不同的框架等等。這就是我今天的全部內容,還有…
GO TO FULL VERSION