草庐IT

记一次重复造轮子(Obsidian 插件设置说明汉化)

Lian 2023-03-28 原文

杂谈 #Java脚本

因本人英语不好在使用Obsidian时,一些插件的设置英文多令人头痛。故有写一个的翻译插件介绍和设置脚本的想法。看到有些前人写的一下翻译方法,简直惨目忍睹。竟然要手动。这个应该写好到只需要一键就可以汉化的地步吗?
好吧。我承认这有些难度。翻译引擎就用有道的吧。我觉得它对专业名词的翻译准确度还是很高的。

  1. 提取main.js中需要的词句
  2. 使用有道API来翻译并生成对应的文件
  3. 使用Quicker的插件一键替换

这里不想详细写过程了,直接贴代码吧。以后有空再整合。
main.js处理代码:(用了FastJson里面的工具,需要导入)

import com.alibaba.fastjson.JSON;

import java.io.*;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author ShuangLian
 * @date 2022/7/4 1:07
 */
public class test {
    public static void main(String[] args) throws IOException {
        // 需要翻译的插件对应的main.js文件
        File file = new File("C:\\Users\\91324\\Documents\\Projects\\IdeaProjects\\Test-demo\\src\\test\\java\\cn\\lian\\main.js");
        System.out.println(file.getAbsolutePath());
        FileInputStream stream = new FileInputStream(file);
        InputStreamReader reader = new InputStreamReader(stream);
        BufferedReader bufferedReader = new BufferedReader(reader);
        LinkedList<String> list = new LinkedList<>();
        String line;
        while ((line = bufferedReader.readLine()) != null) {
            // 匹配要翻译的部分
            String s_setName = "setName\\([^\\)]+\\)";
            String s_addOption = "addOption\\([^\\)]+\\)";
            String s_setDesc = "setDesc\\([^\\)]+\\)*\"\\)";
            String s_name = "name: \".*\",";
            // 使用正则查找匹配
            List<String> linkedList;
            if ((linkedList = find(s_setName, line)).size() != 0) {
                for (String s : linkedList) {
                    list.add(s.substring(9, s.length() - 2));
                }
            }
            if ((linkedList = find(s_setDesc, line)).size() != 0) {
                for (String s : linkedList) {
                    list.add(s.substring(9, s.length() - 2));
                }
            }
            if ((linkedList = find(s_addOption, line)).size() != 0) {
                for (String ss : linkedList) {
                    String substring = ss.substring(10, ss.length() - 1);
                    String[] split = substring.split(",");
                    for (String s : split) {
                        s = s.strip();
                        list.add(s.substring(1, s.length() - 1));
                    }
                }
            }
            if ((linkedList = find(s_name, line)).size() != 0) {
                for (String s : linkedList) {
                    list.add(s.substring(7, s.length() - 2));
                }
            }
        }
        System.out.println(list);
        bufferedReader.close();
        // 输出汉英对照.txt
        File file1 = new File(".\\test2.txt");
        FileOutputStream fileOutputStream = new FileOutputStream(file1);
        // 指定输出文件编码为gbk,不知道为啥做替换插件的那个2b不使用UTF-8
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "gbk");
//        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream);
        BufferedWriter writer = new BufferedWriter(outputStreamWriter);
        for (String s : list) {
            writer.append(s).append("\n");
            String query = FanyiV3Demo.query(s);
            String translation = JSON.parseObject(query).getJSONArray("translation").get(0).toString();
            writer.append(translation).append("\n");
        }
        writer.flush();
    }
    public static List<String> find(String regex, String str) {
        List<String> strings = new LinkedList<>();
        Pattern p = Pattern.compile(regex);
        Matcher matcher = p.matcher(str);
        while (matcher.find()) {
            String fundStr = str.substring(matcher.start(), matcher.end());
            System.out.println(fundStr);
            strings.add(fundStr);
        }
        return strings;
    }
}

有道翻译Java SDK改装


/**
 * @author ShuangLian
 * @date 2022/7/4 3:49
 */

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;


/**
 * 由有道提供,直接调用query方法即可
 * 返回示例:
 * {
 *     "tSpeakUrl": "https://openapi.youdao.com/ttsapi?",
 *     "requestId": "afc14ed2-e6ca-49fe-8f8f-b36e6ff5bbaa",
 *     "query": "Preview on Hover for File Links",
 *     "translation": [
 *         "预览悬停文件链接"
 *     ],
 *     "errorCode": "0",
 *     "dict": {
 *         "url": "yddict://m.youdao.com/dict?le=eng&q=Preview+on+Hover+for+File+Links"
 *     },
 *     "webdict": {
 *         "url": "http://mobile.youdao.com/dict?le=eng&q=Preview+on+Hover+for+File+Links"
 *     },
 *     "l": "en2zh-CHS",
 *     "isWord": false,
 *     "speakUrl": "https://openapi.youdao.com/ttsapi?"
 * }
 */
public class FanyiV3Demo {

    private static Logger logger = LoggerFactory.getLogger(FanyiV3Demo.class);

    private static final String YOUDAO_URL = "https://openapi.youdao.com/api";
	// 去有道查看,这里不能直接用
    private static final String APP_KEY = "1fcc4";
	//
    private static final String APP_SECRET = "LgA5Wxpm60KP7nyuGp";

    public static void main(String[] args) throws IOException {

        Map<String, String> params = new HashMap<String, String>();
        String q = "Decrease body font size";
        String salt = String.valueOf(System.currentTimeMillis());
        params.put("from", "en");
        params.put("to", "zh-CHS");
        params.put("signType", "v3");
        String curtime = String.valueOf(System.currentTimeMillis() / 1000);
        params.put("curtime", curtime);
        String signStr = APP_KEY + truncate(q) + salt + curtime + APP_SECRET;
        String sign = getDigest(signStr);
        params.put("appKey", APP_KEY);
        params.put("q", q);
        params.put("salt", salt);
        params.put("sign", sign);
        params.put("vocabId", "您的用户词表ID");
        /** 处理结果 */
        requestForHttp(YOUDAO_URL, params);
    }

    public static String query(String Str) throws IOException {
        Map<String, String> params = new HashMap<String, String>();
        String q = Str;
        String salt = String.valueOf(System.currentTimeMillis());
        params.put("from", "en");
        params.put("to", "zh-CHS");
        params.put("signType", "v3");
        String curtime = String.valueOf(System.currentTimeMillis() / 1000);
        params.put("curtime", curtime);
        String signStr = APP_KEY + truncate(q) + salt + curtime + APP_SECRET;
        String sign = getDigest(signStr);
        params.put("appKey", APP_KEY);
        params.put("q", q);
        params.put("salt", salt);
        params.put("sign", sign);
        params.put("vocabId", "您的用户词表ID");
        /** 处理结果 */
        return requestForHttp(YOUDAO_URL, params);
    }

    public static String requestForHttp(String url, Map<String, String> params) throws IOException {

        String json = null;
        /** 创建HttpClient */
        CloseableHttpClient httpClient = HttpClients.createDefault();

        /** httpPost */
        HttpPost httpPost = new HttpPost(url);
        List<NameValuePair> paramsList = new ArrayList<NameValuePair>();
        Iterator<Map.Entry<String, String>> it = params.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, String> en = it.next();
            String key = en.getKey();
            String value = en.getValue();
            paramsList.add(new BasicNameValuePair(key, value));
        }
        httpPost.setEntity(new UrlEncodedFormEntity(paramsList, "UTF-8"));
        CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
        try {
            Header[] contentType = httpResponse.getHeaders("Content-Type");
            logger.info("Content-Type:" + contentType[0].getValue());
            if ("audio/mp3".equals(contentType[0].getValue())) {
                //如果响应是wav
                HttpEntity httpEntity = httpResponse.getEntity();
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                httpResponse.getEntity().writeTo(baos);
                byte[] result = baos.toByteArray();
                EntityUtils.consume(httpEntity);
                if (result != null) {//合成成功
                    String file = "合成的音频存储路径" + System.currentTimeMillis() + ".mp3";
                    byte2File(result, file);
                }
            } else {
                /** 响应不是音频流,直接显示结果 */
                HttpEntity httpEntity = httpResponse.getEntity();
                json = EntityUtils.toString(httpEntity, "UTF-8");
                EntityUtils.consume(httpEntity);
                logger.info(json);
                System.out.println(json);
            }
        } finally {
            try {
                if (httpResponse != null) {
                    httpResponse.close();
                }
            } catch (IOException e) {
                logger.info("## release resouce error ##" + e);
            }
        }
        return json;
    }


    /**
     * 生成加密字段
     */
    public static String getDigest(String string) {
        if (string == null) {
            return null;
        }
        char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        byte[] btInput = string.getBytes(StandardCharsets.UTF_8);
        try {
            MessageDigest mdInst = MessageDigest.getInstance("SHA-256");
            mdInst.update(btInput);
            byte[] md = mdInst.digest();
            int j = md.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (byte byte0 : md) {
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str);
        } catch (NoSuchAlgorithmException e) {
            return null;
        }
    }

    /**
     * @param result 音频字节流
     * @param file   存储路径
     */
    private static void byte2File(byte[] result, String file) {
        File audioFile = new File(file);
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(audioFile);
            fos.write(result);

        } catch (Exception e) {
            logger.info(e.toString());
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    public static String truncate(String q) {
        if (q == null) {
            return null;
        }
        int len = q.length();
        String result;
        return len <= 20 ? q : (q.substring(0, 10) + len + q.substring(len - 10, len));
    }
}

还有一个问题:
Quicker插件只是替换功能,有可能会替换到正文的单词。这里应该是需要再使用正则匹配一遍。干脆改成只用java来处理吧。

有关记一次重复造轮子(Obsidian 插件设置说明汉化)的更多相关文章

  1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  2. ruby-openid:执行发现时未设置@socket - 2

    我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass

  3. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  4. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

    在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

  5. ruby-on-rails - 无法使用 Rails 3.2 创建插件? - 2

    我对最新版本的Rails有疑问。我创建了一个新应用程序(railsnewMyProject),但我没有脚本/生成,只有脚本/rails,当我输入ruby./script/railsgeneratepluginmy_plugin"Couldnotfindgeneratorplugin.".你知道如何生成插件模板吗?没有这个命令可以创建插件吗?PS:我正在使用Rails3.2.1和ruby​​1.8.7[universal-darwin11.0] 最佳答案 随着Rails3.2.0的发布,插件生成器已经被移除。查看变更日志here.现在

  6. ruby-on-rails - 如何使用 instance_variable_set 正确设置实例变量? - 2

    我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击

  7. ruby-on-rails - date_field_tag,如何设置默认日期? [ rails 上的 ruby ] - 2

    我想设置一个默认日期,例如实际日期,我该如何设置?还有如何在组合框中设置默认值顺便问一下,date_field_tag和date_field之间有什么区别? 最佳答案 试试这个:将默认日期作为第二个参数传递。youcorrectlysetthedefaultvalueofcomboboxasshowninyourquestion. 关于ruby-on-rails-date_field_tag,如何设置默认日期?[rails上的ruby],我们在StackOverflow上找到一个类似的问

  8. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

  9. ruby-on-rails - 有没有办法为 CarrierWave/Fog 设置上传进度指示器? - 2

    我在Rails应用程序中使用CarrierWave/Fog将视频上传到AmazonS3。有没有办法判断上传的进度,让我可以显示上传进度如何? 最佳答案 CarrierWave和Fog本身没有这种功能;你需要一个前端uploader来显示进度。当我不得不解决这个问题时,我使用了jQueryfileupload因为我的堆栈中已经有jQuery。甚至还有apostonCarrierWaveintegration因此您只需按照那里的说明操作即可获得适用于您的应用的进度条。 关于ruby-on-r

  10. spring.profiles.active和spring.profiles.include的使用及区别说明 - 2

    转自:spring.profiles.active和spring.profiles.include的使用及区别说明下文笔者讲述spring.profiles.active和spring.profiles.include的区别简介说明,如下所示我们都知道,在日常开发中,开发|测试|生产环境都拥有不同的配置信息如:jdbc地址、ip、端口等此时为了避免每次都修改全部信息,我们则可以采用以上的属性处理此类异常spring.profiles.active属性例:配置文件,可使用以下方式定义application-${profile}.properties开发环境配置文件:application-dev

随机推荐