创建网站商城,免费的ppt制作软件,企业网站的策划书,南昌房产网信息网本片文章教大家怎样在spring boot项目中引入百度翻译#xff0c;并且优雅的使用百度翻译。
首先#xff0c;我们要了解为什么要使用翻译插件。为了支持多语言的国际化#xff1b;
目前市面上最常见的后端国际化就是在resource资源目录下设置多个语言文档#xff0c;这些文…本片文章教大家怎样在spring boot项目中引入百度翻译并且优雅的使用百度翻译。
首先我们要了解为什么要使用翻译插件。为了支持多语言的国际化
目前市面上最常见的后端国际化就是在resource资源目录下设置多个语言文档这些文档中是一些key、value类型的键值对然后我们展示的时候语言不直接写死而是通过使用key的形式来进行引用从而实现国际化的方式。但是这种方式有很大的局限性。我们要做的国际化只能是一些提前写死的键值。这就很不友好了
我们公司的业务需求是我只要点击切换语言之后。中文的就是中文环境点击切换英文之后就是英文环境了。这就需要我们至少需要多套语言环境如果是中文那么所有的中文数据都放在中文的数据库中如果是英文那么所有的英文数据都放在英文的数据库。多语言的环境直接切换出来。这个时候就需要我们就行翻译了直接把一整条数据就行翻译饭后插入到不同的语言环境(数据库)中。本次使用百度的翻译插件
1、进入百度翻译官网注册账号
百度翻译开放平台
进入官网登录、注册并且申请相应的账号。 然后点击产品服务选择相应的服务类型 最后在管理控制台的开发者信息中找到自己额度appID和相应的密钥 2、创建spring boot项目整合翻译接口
由于我们调用百度的翻译接口时通过网络请求来调用的所以我们不需要引入任何第三方的百度APi依赖。
基本的maven依赖如下 !-- 依赖配置 --dependenciesdependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!-- 自动装配依赖--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-autoconfigure/artifactId/dependency!--自动装配依赖非必需依赖,该依赖作用是在使用IDEA编写配置文件有代码提示--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-configuration-processor/artifactId/dependencydependencygroupIdcom.alibaba.fastjson2/groupIdartifactIdfastjson2/artifactId/dependency/dependencies
我想要实现的功能是传入一个Object类型的对象或者泛型T。不管这个对象中有多少属性我们只翻译其中的String类型的属性并且这个String类型的属性值不为null并且不为空字符串。并且自定义了一个注解只要在属性值上加上了这个注解那么这个属性值也不会被翻译了。
最后我想要写成翻译的jar包打到我们公司的maven私服上。只要在项目中引入了翻译的maven坐标就可以直接使用了。
自定义一个注解这个注解的作用是加上这个注解的属性值不会被翻译。
/*** 不进行翻译的字段注解*/
Target(ElementType.FIELD) // 只能应用于字段
Retention(RetentionPolicy.RUNTIME) // 运行时保留便于反射获取
public interface NoTranslation {// 可选添加属性如原因说明String reason() default ;
}
自定义属性类用来获取到百度翻译的一些属性值。
ConfigurationProperties(tools.translate.baidu)
Component
Data
public class BaiDuTranslateParams {/*** 百度翻译的appId*/private String appId;/*** 百度翻译的密钥*/private String secretKey;/*** 翻译的源语言默认为自动检测语言*/private String fromauto;/*** 翻译的目标语言默认为英文*/private String toen;/*** 翻译的url*/private String url;
}
翻译的url是根据你选择的不同翻译服务来的可以去百度翻译的官网查看相应的翻译地址。我使用的是百度的通用翻译所以引用百度的通用翻译的url地址。 设置百度翻译的MD5工具类这个百度翻译的MD5工具是百度翻译官方提供的所以不是随便的一个MD5生成器就可以使用的。
/*** MD5编码相关的类* * author wangjingtao* */
public class MD5 {// 首先初始化一个字符数组用来存放每个16进制字符private static final char[] hexDigits { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d,e, f };/*** 获得一个字符串的MD5值* * param input 输入的字符串* return 输入字符串的MD5值* */public static String md5(String input) {if (input null)return null;try {// 拿到一个MD5转换器如果想要SHA1参数换成”SHA1”MessageDigest messageDigest MessageDigest.getInstance(MD5);// 输入的字符串转换成字节数组byte[] inputByteArray input.getBytes(StandardCharsets.UTF_8);// inputByteArray是输入字符串转换得到的字节数组messageDigest.update(inputByteArray);// 转换并返回结果也是字节数组包含16个元素byte[] resultByteArray messageDigest.digest();// 字符数组转换成字符串返回return byteArrayToHex(resultByteArray);} catch (NoSuchAlgorithmException e) {return null;}}/*** 获取文件的MD5值* * param file* return*/public static String md5(File file) {try {if (!file.isFile()) {System.err.println(文件 file.getAbsolutePath() 不存在或者不是文件);return null;}FileInputStream in new FileInputStream(file);String result md5(in);in.close();return result;} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return null;}public static String md5(InputStream in) {try {MessageDigest messagedigest MessageDigest.getInstance(MD5);byte[] buffer new byte[1024];int read 0;while ((read in.read(buffer)) ! -1) {messagedigest.update(buffer, 0, read);}in.close();String result byteArrayToHex(messagedigest.digest());return result;} catch (NoSuchAlgorithmException e) {e.printStackTrace();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return null;}private static String byteArrayToHex(byte[] byteArray) {// new一个字符数组这个就是用来组成结果字符串的解释一下一个byte是八位二进制也就是2位十六进制字符2的8次方等于16的2次方char[] resultCharArray new char[byteArray.length * 2];// 遍历字节数组通过位运算位运算效率高转换成字符放到字符数组中去int index 0;for (byte b : byteArray) {resultCharArray[index] hexDigits[b 4 0xf];resultCharArray[index] hexDigits[b 0xf];}// 字符数组组合成字符串返回return new String(resultCharArray);}}设置百度翻译的响应结果的工具类
Data
public class BaiduTranslationUtils{private String from;private String to;private ListTranslationResult trans_result;// // 获取第一个翻译结果安全处理空列表
// public TranslationResult getFirstTranslation() {
// return trans_result ! null !trans_result.isEmpty() ?
// trans_result.getFirst() : null;
// }// 内部类表示单个翻译结果Datapublic static class TranslationResult {private String src;private String dst;}
} 设置HTTP的发送请求工具类本次使用HTTPClient来继续HTTP请求的发送。
Component
RequiredArgsConstructor
public class HttpClientUtils {private final RestTemplate restTemplate;/*** 发送 application/x-www-form-urlencoded 格式的 POST 请求* param url 请求地址* param formData 表单数据 (键值对)* param responseType 返回类型* return ResponseEntity 包含响应体和状态码*/public T ResponseEntityT postForm(String url,MultiValueMapString, Object formData,ClassT responseType) {HttpHeaders headers new HttpHeaders();headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);HttpEntityMultiValueMapString, Object entity newHttpEntity(formData, headers);return restTemplate.exchange(url,HttpMethod.POST,entity,responseType);}/*** 发送 application/x-www-form-urlencoded 格式的 POST 请求Map 简化版* param url 请求地址* param formData 表单数据 (键值对)* param responseType 返回类型* return ResponseEntity 包含响应体和状态码*/public T ResponseEntityT postForm(String url,MapString, String formData,ClassT responseType) {MultiValueMapString, Object multiValueMap new LinkedMultiValueMap();formData.forEach(multiValueMap::add);return postForm(url, multiValueMap, responseType);}public T ResponseEntityT request(String url,HttpMethod method,Object requestBody,HttpHeaders headers,MultiValueMapString, String params,ClassT responseType) {// 构建带参数的URLUriComponentsBuilder builder UriComponentsBuilder.fromHttpUrl(url);if (params ! null !params.isEmpty()) {builder.queryParams(params);}String finalUrl builder.build().encode().toUriString();HttpEntityObject entity new HttpEntity(requestBody, headers);return restTemplate.exchange(finalUrl, method, entity, responseType);}}
设置百度翻译的发送请求的service类用来发送百度请求接收一个ListString类型的参数接收翻译的源语言和目标语言。
Service
RequiredArgsConstructor
Slf4j
public class BaiduTranslateService {private final BaiDuTranslateParams config;
private final ObjectMapper objectMapper;private final HttpClientUtils httpClient;public ListString translate(ListString texts, String from, String to) throws Exception {String salt String.valueOf(System.currentTimeMillis());String query String.join(\n, texts);String mdconfig.getAppId() query salt config.getSecretKey();String sign MD5.md5(md);MapString, Object params new HashMap();params.put(q, query);params.put(from, from);params.put(to, to);params.put(appid, config.getAppId());params.put(salt, salt);params.put(sign, sign);MultiValueMapString, Object requestParams new LinkedMultiValueMap();requestParams.setAll(params);ResponseEntityObject response httpClient.postForm(config.getUrl(), requestParams, Object.class);Object body response.getBody();if (body null) {return Collections.singletonList(翻译失败);}BaiduTranslationUtils result objectMapper.readValue(JSON.toJSONString(body), BaiduTranslationUtils.class);return result.getTrans_result().stream().map(BaiduTranslationUtils.TranslationResult::getDst).collect(Collectors.toList());}
}
设置翻译发送的工具类用来发送翻译的请求。我们想要实现的这样我传入一个T泛型的对象
然后返回一个T泛型的对象。只翻译这个对象中的String类型的属性其他属性不翻译。String类型的属性也不是说全部翻译的String属性为null或者为空字符串或者在String属性上加上了NoTranslation不翻译注解。这几种情况下的String属性都不进行翻译。
工具类中有三个方法
1、传入T 泛型对象返回T翻译好的对象。
2、传入T泛型对象传入翻译的源语言传入翻译的目标语言。返回T翻译好的对象
3、传入ListT泛型对象传入翻译的源语言传入翻译的目标语言。返回T翻译好的对象
要注意百度翻译的字符数量一次大概限定在6000个字符换算成文字大概为2000个汉字。
相应的翻译工具类如下
/*** Author 张乔* Date 2025/5/29 14:21*/
Component
RequiredArgsConstructor
Slf4j
public class BaiduTranslationSend {private static final Logger logger LoggerFactory.getLogger(BaiduTranslationSend.class);private final BaiduTranslateService translateService;private final BaiDuTranslateParams baiDuTranslateParams;/*** 批量翻译对象列表中所有标记为可翻译的字段* 该方法遍历对象列表中的每个对象识别带有Translatable注解的字段收集需要翻译的文本* 通过翻译服务进行批量翻译最后将翻译结果回写到原对象的对应字段中。** param T 对象类型* param objList 待翻译的对象列表允许为null或空列表* param fromLang 原始语言代码支持0/1/2数字代码自动转换* param toLang 目标语言代码支持0/1/2数字代码自动转换* return 翻译后的对象列表发生错误时返回原列表*/public T ListT translateObjectFieldsList(ListT objList, String fromLang, String toLang) {if (objList null || objList.isEmpty()) {logger.warn(传入对象列表为空或空列表无法进行翻译操作。);return objList;}// 语言代码转换复用单个对象的转换逻辑MapString, String langCodeMap Map.of(0, zh, 1, en, 2, jp);fromLang langCodeMap.getOrDefault(fromLang, fromLang);toLang langCodeMap.getOrDefault(toLang, toLang);try {// 1. 收集所有需要翻译的文本及其元数据ListTextTranslationTask translationTasks new ArrayList();MapClass?, ListField classFieldCache new HashMap();for (T obj : objList) {if (obj null) continue;// 获取或缓存类字段信息ListField translatableFields classFieldCache.computeIfAbsent(obj.getClass(),this::getTranslatableFields);for (Field field : translatableFields) {try {String value (String) field.get(obj);if (isValidForTranslation(value)) {translationTasks.add(new TextTranslationTask(obj, field, value));}} catch (IllegalAccessException e) {logger.warn(对象 [{}] 字段 [{}] 访问失败: {},obj.getClass().getSimpleName(), field.getName(), e.getMessage());}}}// 2. 如果没有需要翻译的内容提前返回if (translationTasks.isEmpty()) {logger.info(列表中共 {} 个对象均无可翻译字段, objList.size());return objList;}// 3. 提取所有需要翻译的文本ListString textsToTranslate translationTasks.stream().map(task - task.originalText).collect(Collectors.toList());// 4. 批量翻译支持自动分批处理大量文本ListString translatedTexts translateService.translate(textsToTranslate, fromLang, toLang);// 5. 应用翻译结果到所有对象applyBatchTranslations(translationTasks, translatedTexts);logger.info(成功翻译 {} 个对象中的 {} 个字段,objList.size(), translationTasks.size());return objList;} catch (Exception e) {logger.error(批量翻译对象列表时出错: {}, e.getMessage(), e);return objList; // 出错时返回原列表}}// 翻译任务内部类记录文本的元数据private static class TextTranslationTask {final Object targetObject;final Field targetField;final String originalText;TextTranslationTask(Object targetObject, Field targetField, String originalText) {this.targetObject targetObject;this.targetField targetField;this.originalText originalText;}}// 批量应用翻译结果private void applyBatchTranslations(ListTextTranslationTask tasks, ListString translatedTexts) {if (tasks.size() ! translatedTexts.size()) {logger.error(翻译结果数量 {} 与任务数量 {} 不匹配,translatedTexts.size(), tasks.size());return;}for (int i 0; i tasks.size(); i) {TextTranslationTask task tasks.get(i);String translatedText translatedTexts.get(i);try {task.targetField.set(task.targetObject, translatedText);} catch (IllegalAccessException e) {logger.error(设置对象 [{}] 字段 [{}] 翻译值失败: {},task.targetObject.getClass().getSimpleName(),task.targetField.getName(),e.getMessage());}}}// 复用单个对象翻译的字段获取方法private ListField getTranslatableFields(Class? clazz) {return TRANSLATABLE_FIELD_CACHE.computeIfAbsent(clazz, k - {ListField fields new ArrayList();Class? current clazz;while (current ! null current ! Object.class) {for (Field field : current.getDeclaredFields()) {if (field.getType() String.class !field.isAnnotationPresent(NoTranslation.class)) {field.setAccessible(true);fields.add(field);}}current current.getSuperclass();}return Collections.unmodifiableList(fields);});}/*** 翻译对象的字符串字段。** param T 对象的类型* param obj 需要翻译的对象*/public T T translateObjectFields(T obj) {// 1. 空对象检查if (obj null) {logger.warn(传入对象为空无法进行翻译操作。);return null;}try {// 3. 获取可翻译字段带缓存ListField translatableFields getTranslatableFields(obj.getClass());// 4. 收集需要翻译的字段值MapField, String fieldValueMap new LinkedHashMap();ListString textsToTranslate new ArrayList();for (Field field : translatableFields) {try {String value (String) field.get(obj);if (isValidForTranslation(value)) {fieldValueMap.put(field, value);textsToTranslate.add(value);}} catch (IllegalAccessException e) {logger.warn(字段 [{}] 访问失败: {}, field.getName(), e.getMessage());}}// 5. 无翻译内容提前返回if (textsToTranslate.isEmpty()) {logger.debug(类型 [{}] 无可翻译字段, obj.getClass().getSimpleName());return obj;}// 6. 执行批量翻译ListString translatedTexts translateService.translate(textsToTranslate,baiDuTranslateParams.getFrom(), baiDuTranslateParams.getTo());// 7. 回填翻译结果带安全检查applyTranslations(obj, fieldValueMap, translatedTexts);logger.info(成功翻译 {} 个字段: [{}],fieldValueMap.size(), obj.getClass().getSimpleName());return obj;} catch (Exception e) {logger.error(翻译对象 [{}] 时出错: {},obj.getClass().getSimpleName(), e.getMessage(), e);return obj; // 出错时返回原对象而非null}}/*** 翻译对象中的字符串字段。** param T 对象类型* param obj 需要翻译的对象* param fromLang 源语言* param toLang 目标语言*/// 类级缓存避免重复反射扫描private static final MapClass?, ListField TRANSLATABLE_FIELD_CACHE new ConcurrentHashMap();public T T translateObjectFields(T obj, String fromLang, String toLang) {// 1. 空对象检查if (obj null) {logger.warn(传入对象为空无法进行翻译操作。);return null;}// 2. 语言代码转换使用Map更灵活MapString, String langCodeMap Map.of(0, zh, 1, en, 2, jp);fromLang langCodeMap.getOrDefault(fromLang, fromLang);toLang langCodeMap.getOrDefault(toLang, toLang);try {// 3. 获取可翻译字段带缓存ListField translatableFields getTranslatableFields(obj.getClass());// 4. 收集需要翻译的字段值MapField, String fieldValueMap new LinkedHashMap();ListString textsToTranslate new ArrayList();for (Field field : translatableFields) {try {String value (String) field.get(obj);if (isValidForTranslation(value)) {fieldValueMap.put(field, value);textsToTranslate.add(value);}} catch (IllegalAccessException e) {logger.warn(字段 [{}] 访问失败: {}, field.getName(), e.getMessage());}}// 5. 无翻译内容提前返回if (textsToTranslate.isEmpty()) {logger.debug(类型 [{}] 无可翻译字段, obj.getClass().getSimpleName());return obj;}// 6. 执行批量翻译ListString translatedTexts translateService.translate(textsToTranslate, fromLang, toLang);// 7. 回填翻译结果带安全检查applyTranslations(obj, fieldValueMap, translatedTexts);logger.info(成功翻译 {} 个字段: [{}],fieldValueMap.size(), obj.getClass().getSimpleName());return obj;} catch (Exception e) {logger.error(翻译对象 [{}] 时出错: {},obj.getClass().getSimpleName(), e.getMessage(), e);return obj; // 出错时返回原对象而非null}}// 判断字符串是否适合翻译private boolean isValidForTranslation(String value) {return value ! null !value.trim().isEmpty();}// 应用翻译结果到对象字段private T void applyTranslations(T obj, MapField, String fieldMap, ListString translations) {if (fieldMap.size() ! translations.size()) {logger.error(翻译结果数量 {} 与字段数量 {} 不匹配,translations.size(), fieldMap.size());return;}int index 0;for (Map.EntryField, String entry : fieldMap.entrySet()) {Field field entry.getKey();try {field.set(obj, translations.get(index));} catch (IllegalAccessException e) {logger.error(设置字段 [{}] 翻译值失败: {}, field.getName(), e.getMessage());}}}}
然后记得打成jar包的形式。
如果不知道第三方spring boot的jar包怎样打的可以查看一下我的这篇文章。
springboot3自定义starter详细入门-CSDN博客
然后在项目中引入下相应的maven坐标。在yml配置文件中引入相应的百度翻译的属性值。 那么现在就可以使用这个翻译配置类了。
在Test测试类中写一个测试方法 相应的运行结果如下 因为这个属性里面只有两个属性是String类型需要翻译所以只翻译了两个属性。我这边源语言是百度自动识别目标语言是小日子语。
翻译工具类的原理是收集传入T泛型的所有需要翻译的String属性值统一存放在一个ListString中然后在通过拼接换行符\n的形式来发送翻译请求。 百度翻译相应的参数中有翻译的原始数据和翻译之后的目标数据通过进行比对再把翻译后的目标数据填充到相应的泛型对象T中。 以上就是正片文章的内容了。如果觉得文章写的不错请给博主点个赞非常感谢