Zotero PDF翻译插件自定义翻译功能实现

news2024/11/16 19:52:21

Zotero PDF翻译插件自定义翻译功能实现

  • 一 需求
  • 二、实现演示
  • 三、Zetero翻译插件的功能定制
    • 3.1 开发环境
    • 3.2 开发步骤
    • 3.3 插件开发实现
      • 3.3.1 添加服务
      • 3.3.2 编写任务处理程序
      • 3.3.3 导入任务处理程序
      • 3.3.4 添加服务名称
      • 3.3.5 构建
  • 四、服务器的开发
    • 4.1 环境
    • 4.2 代码实现

一 需求

在使用Zotero PDF翻译插件的时候,感觉总是受到一些限制。Google翻译在国内无法使用,百度、deepl等翻译,都需要key。所以很大程度上,无法直接进行划词翻译。最常见的操作就是复制到浏览器中进行翻译。现在的需求就是:
** 能不能将复制这个动作自动化?**

我的思路就是:
对Zotero 插件进行修改,使其能够支持自定义的服务器访问。然后将需要翻译的内容,在服务器上,利用Pytho的爬虫功能,实现自动化的操作浏览器,并将结果返回到Zotero。

二、实现演示

在这里插入图片描述

三、Zetero翻译插件的功能定制

3.1 开发环境

nodejs

3.2 开发步骤

依据官网的介绍,开发的具体步骤如下:

插件是基于Zotero插件模板.

可以运行如下命令进行构建:

git clone https://github.com/windingwind/zotero-pdf-translate.git
cd zotero-pdf-translate
npm install
npm run build

插件生成在: ./builds/*.xpi.

添加新的翻译服务:

  1. 添加新的服务配置到: src/utils/config.ts > SERVICES;
  2. 添加一个与其他服务格式相同的新的任务处理过程到src/modules/services/${serviceId}.ts 。 如果>程序运行成功,导出函数将翻译的结果放在 data.result 中,如果运行失败,则抛出一个异常;
  3. src/modules/services.ts中导入任务处理函数
  4. addon/chrome/locale/${lang}/addon.properties中添加本地语言的服务名称: service.${serviceId}
  5. 构建与测试

3.3 插件开发实现

3.3.1 添加服务

在这里相当于要用建立一个自定义服务的对象
服务配置

3.3.2 编写任务处理程序

文件位置如下图所示:
localserver.ts
图中代码如下:


import { TranslateTask, TranslateTaskProcessor } from "../../utils/translate";

export default <TranslateTaskProcessor>async function (data) {
    const url = data.secret;
    const reqBody = `data=${encodeURIComponent(data.raw)}&from=${encodeURIComponent(data.langfrom)}&to=${encodeURIComponent(data.langto)}`;
    const xhr = await Zotero.HTTP.request("POST", url, {
    responseType: "json",
    body: reqBody,
  });
  if (xhr?.status !== 200) {
    throw `Request error: ${xhr?.status}`;
  }
  data.result = xhr.response.data;
};

3.3.3 导入任务处理程序

添加服务

3.3.4 添加服务名称

添加服务名称

3.3.5 构建

构建命令如下:

npm run build

构建后的插件目录:
目录
插件安装后的效果:
安装后的效果
运行界面

四、服务器的开发

4.1 环境

python:

  1. flask作为服务器
  2. selenium:作为自动化工具

4.2 代码实现

from flask import Flask, jsonify,request
import logging
from urllib.parse import urlparse
import seleniumwire.undetected_chromedriver as uc
from  selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import pyperclip
import time
import os
app = Flask(__name__)

class Translate():
    def __init__(self,options=None,sw_options={},isUC=False) -> None:

        self.opened_domain = {}

        if options is None:
            options = uc.ChromeOptions()
        assert isinstance(options,uc.ChromeOptions)

        self.options = options
        self.sw_options = sw_options
        if isUC:
            self.driver = uc.Chrome(
                options=self.options,
                seleniumwire_options=self.sw_options
            )
        else:
            self.driver = webdriver.Chrome(options=self.options)
    
    def openUrl(self,url):
        domain = urlparse(url).netloc
        handles = self.driver.window_handles
        # 判断当前浏览器是否打开
        if len(self.opened_domain) < 1:
            self.driver.get(url)
            self.opened_domain[domain] = self.driver.current_window_handle
        elif domain not in  self.opened_domain.keys():
            self.driver.execute_script(f"window.open('{url}', '_blank')")
            wait = WebDriverWait(self.driver, timeout=10, poll_frequency=1, ignored_exceptions=[TimeoutException])    
            wait.until(EC.new_window_is_opened(handles))
            new_handles = self.driver.window_handles
            new_handle = [handle for handle in new_handles if handle not in handles]
            assert new_handle is not []
            self.opened_domain[domain] = new_handle[0]
        self.driver.switch_to.window(self.opened_domain[domain])        
    def translate(self,url:str,data:str,transFun:callable):
        self.openUrl(url)
        if transFun is None:
            print("翻译函数为空!!!")
            return
        result = transFun(data)
        # data = {'data': result}
        return result
        # return jsonify(data)
    
    def deepl(self,data):
        if EC.visibility_of_element_located((By.XPATH, '//*[@id="panelTranslateText"]/div[1]/div[2]/section[1]/div[3]/div[2]/d-textarea/div'))(self.driver):
            clear_button = self.driver.find_element(By.XPATH, '//*[@id="panelTranslateText"]/div[1]/div[2]/section[1]/div[3]/div[2]/d-textarea/div')
            clear_button.click()
        lang = self.driver.find_element(By.TAG_NAME,"html").get_attribute('lang')
        if lang == "en":
            tag_text = "Like translation"
            copy_text = "Copy to clipboard"
        elif lang =="zh":
            tag_text = "喜欢该翻译"
            copy_text = "复制到剪贴板"
        
        el_input = WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.XPATH, '//*[@id="panelTranslateText"]/div[1]/div[2]/section[1]/div[3]/div[2]/d-textarea/div')))
        el_input.clear()
        el_input.click()
        el_input.send_keys(data)
        WebDriverWait(self.driver, timeout=10).until(lambda d: d.find_element(By.CSS_SELECTOR,f'button[aria-label="{tag_text}"]'))
        time.sleep(2)
        copy_button = self.driver.find_element(By.CSS_SELECTOR,f'button[data-testid="translator-target-toolbar-copy"][aria-label="{copy_text}"]')                
        copy_button.click()
        result = pyperclip.paste()
        return result
    
    def deeplFY(self,input,_from="en",_to="zh"):
        base_url  = f"https://www.deepl.com/translator#{_from}/{_to}/"
        result = self.translate(base_url,input,self.deepl)
        return result
        
    def baidu(self,data):
        submit = WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.XPATH, '//*[@id="baidu_translate_input"]')))
        submit.clear()
        submit.send_keys(data)
        el_output = WebDriverWait(self.driver, timeout=10).until(lambda d: d.find_element(By.XPATH,'//*[@id="main-outer"]/div/div/div[1]/div[2]/div[1]/div[2]/div/div/div[1]/p[2]'))
        result = el_output.text
        return result
    
    def bdFY(self,data:str="Hello",_from:str="en",_to:str="zh"):
        url = f"https://fanyi.baidu.com/translate#{_from}/{_to}"
        return self.translate(url,data,self.baidu)


transDriver = Translate()
log_file_name = 'logger-' + time.strftime('%Y-%m-%d', time.localtime(time.time())) + '.log'
log_file_path = os.path.join(os.path.abspath(os.curdir),log_file_name)

handler = logging.FileHandler(log_file_path,encoding='UTF-8')
logging_format = logging.Formatter(
    '%(asctime)s-%(message)s')
handler.setFormatter(logging_format)
app.logger.addHandler(handler)
print(f"log location:{log_file_path}")



@app.route('/', methods=['POST','GET'])
def root():
    rq_data = request.form.get("data")
    print(rq_data)
    # 在这里编写API逻辑
    translators = {
        "BAIDUI":'/bdFY',
        "DEEPL":'/deeplFY'
    }
    data = {'data': translators}
    return jsonify(data)

@app.route('/bdFY',methods=['POST'])
def bdFY():
    _data = request.form.get("data")
    _from = request.form.get("from")
    _to = request.form.get("to")
    app.logger.info(f"data:{_data}")
    try:
        result = transDriver.bdFY(_data)
    except Exception as e:
        result = str(e)
    app.logger.info(f"result:{result}")
    return jsonify({'data':result})

@app.route('/deeplFY',methods=['POST'])
def deeplFY():
    _data = request.form.get("data")
    _from = request.form.get("from")
    _to = request.form.get("to")
    app.logger.info(f"data:{_data}")
    try:
        result = transDriver.deeplFY(_data)
    except Exception as e:
        result = str(e)
    app.logger.info(f"result:{result}")
    return jsonify({'data':result})

if __name__ == '__main__':
    
    app.run(port=80)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/642416.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java开发——shell编程

java开发——shell编程 shell是什么东西&#xff1f;java程序员为什么要学习Shell?Shell的解析器第一个Shell脚本Shell中的变量Shell的运算符Shell的条件判断Shell的if语句Shell的case语句Shell的for语句Shell的while语句Shell控制台输入Shell的系统函数Shell自定义函数Shell工…

MySQL InnoDB集群部署及管理全教程(二)

MySQL InnoDB集群部署及管理全教程&#xff08;一&#xff09;_Doker 多克的博客-CSDN博客 四、 部署生产 InnoDB 集群 在生产环境中工作时&#xff0c;组成InnoDB集群的MySQL服务器实例作为网络的一部分在多台主机上运行&#xff0c;而不是在第6.8节“AdminAPI MySQL Sandbo…

Unsupervised Learning(无监督学习)

目录 Introduction Clustering&#xff08;聚类&#xff09; Dimension Reduction&#xff08;降维&#xff09; PCA&#xff08;Principle component analysis&#xff0c;主成分分析&#xff09; Word Embedding&#xff08;词嵌入&#xff09; Matrix Factorization(矩…

Linux之用户管理

目录 Linux之用户管理 添加新用户 --- useradd命令 语法格式 常用选项 案例 为用户账号设置密码 -- passwd 语法格式 选项及作用 案例 修改用户属性 --- usermod命令 语法格式 选项及作用 案例 删除用户账号 --- userdel命令 语法格式 选项及作用 案例 用户切换 …

imu绘制轨迹

本文作者感谢武汉大学卫星导航定位技术研究中心多源智能导航实验室(i2Nav)牛小骥教授团队开源的KF-GINS软件平台。 首先声明&#xff0c;仅仅是实现&#xff0c;实际应用意义不大 这套算法利用EKF更新误差并且补偿到状态更新&#xff0c;如果没有gps信号时&#xff0c;利用im…

jetpack compose —— Card

jetpack compose Card 组件提供了一种简单而强大的方式来呈现卡片式的用户界面。 一、什么是 Card 组件 二、基本用法 三、属性和修饰符 四、嵌套使用和复杂布局 一、什么是 Card 组件 Card 是 Jetpack Compose 中的一个常用组件&#xff0c;用于呈现卡片式的用户界面。它…

量化投资 现代投资组合理论(MPT)

量化投资 现代投资组合理论&#xff08;MPT&#xff09; 问题&#xff1a;构建投资组合&#xff0c;达到目标收益率的同时拥有最小的 risk exposure. 有 J J J 个可交易证券&#xff0c;期望收益率为 R [ R 1 , ⋯ , R j ] T R[R_1,\,\cdots,\,R_j]^T R[R1​,⋯,Rj​]T&…

小红书数据洞察!父亲节将临,3大种草方式打动消费者

父亲节即将来临&#xff0c;各大社交平台陆陆续续开始讨论。品牌自然也不会错过此机&#xff0c;走心宣传。那么&#xff0c;今年的父亲节&#xff0c;有哪些热门内容呢&#xff1f;品牌如何点燃消费热情、提升形象&#xff1f;通过小红书数据和关键词分析&#xff0c;我们进一…

基于阿里云 Serverless 容器服务轻松部署企业级 AI 应用

作者&#xff1a;元毅、坤仑 数禾科技 AI 模型服务基于云原生架构&#xff0c;为不同业务环节提供智能决策支持。随着业务的快速发展&#xff0c;摆在数禾面前的难题是支撑模型计算的底层应用资源无法根据请求量来调整机器资源支持运算能力。同时&#xff0c;随着模型在线推理…

医疗行业的新选择:智能医疗管理模板

随着社会的发展&#xff0c;医疗行业也在不断地进步与发展&#xff0c;信息化已经成为医疗行业的重要一环。智能医疗管理应用作为新型医疗管理工具&#xff0c;已经成为中小型医院、门诊、美容机构等企业的必备软件之一。该应用包括患者管理、预约管理、诊断管理、住院管理、财…

Qt推荐的多线程的理解

目的 在Qt4.8之后&#xff0c;Qt多线程的写法最好还是通过QObject来实现&#xff0c;和线程的交互通过信号和槽(实际上其实是通过事件)联系。 用QObject来实现多线程有个非常好的优点&#xff0c;就是默认就支持事件循环&#xff08;Qt的许多非GUI类也需要事件循环支持&#x…

js执行顺序:

这篇笔记摘录来源&#xff1a; &#x1f449;我是javascript&#xff0c;2分钟彻底弄懂我的执行机制&#xff1f;【JavaScript教程】_哔哩哔哩_bilibili &#x1f449;js执行顺序_前端小白&#xff0c;请多指教的博客-CSDN博客 目录 面试题&#xff1a; 一、单线程 二、…

十年软件测试经验,我的成长之道

有很多小伙伴问&#xff0c;测试职业的天花板是不是很低&#xff1f; 在回答这个问题之前&#xff0c;我想请大家先想一下&#xff0c;当初自己为什么会选择测试这个职业&#xff1f;入门门槛低&#xff1f;不需要写代码&#xff1f;工作比开发轻松&#xff1f;还是其他。这个…

科技政策 | 工业和信息化部办公厅关于组织申报2023年跨行业跨领域工业互联网平台的通知

原创 | 文 BFT机器人 6月1日工业和信息化部办公厅发布关于组织申报2023年跨行业跨领域工业互联网平台的通知&#xff1b;旨在贯彻《国务院关于深化“互联网先进制造业”发展工业互联网的指导意见》&#xff0c;落实《工业互联网创新发展行动计划&#xff08;2021-2023年&#…

还有多少公司在使用H5?不怕被破解吗?

H5还有人在用吗 近几天&#xff0c;老板让我调查一下现在市面上H5的使用现状。粗略地调查了一下&#xff0c;发现现在使用H5的真不多了&#xff0c;但是还是有人在用H5的&#xff0c;原因无非一是成本低&#xff0c;相比户外广告&#xff0c;H5制作费用根本不值一提&#xff1…

Delta 一个新的 git diff 对比显示工具

目录 介绍git diff 介绍delta介绍 一、安装1.下载 Git2.下载 delta3.解压4.修改配置文件5. 修改主题6.其他配置和说明 二、对比命令1.在项目中 git diff 常用命令2.对比电脑上两个文件3.对比电脑上的两个文件夹 三、在Git 命令行中使用效果四、在idea 的Terminal命令行中使用效…

展览展会邀请媒体现场报道需要注意什么?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 真开心今天与大家分享展览展会邀请媒体的一些经验&#xff0c;行业复苏&#xff0c;各行各业都充满了活力&#xff0c;每天胡老师都会接到大大小小展会邀请媒体报道的需求&#xff0c;那…

终端数据防泄漏

场景描述 科技研发类公司在日常工作中&#xff0c;存在员工对源代码数据有意或者无意的传播。软件企业作为以源代码数据为核心资产的行业&#xff0c;如果数据安全管理不规范&#xff0c;会出现员工把核心数据一锅端&#xff0c;使企业失去竞争力&#xff0c;造成不可估计的损…

代码随想录算法训练营第五十六天|583. 两个字符串的删除操作|72. 编辑距离

LeetCode583. 两个字符串的删除操作 动态规划五部曲&#xff1a; 1&#xff0c;确定dp数组&#xff08;dp table&#xff09;以及下标的含义&#xff1a;dp[i][j]&#xff1a;以i-1为结尾的字符串word1&#xff0c;和以j-1位结尾的字符串word2&#xff0c;想要达到相等&#…

自己尝试在springboot2.0微服务中内嵌一个FTP Server

1.pom.xml添加依赖 <dependency><groupId>org.apache.ftpserver</groupId><artifactId>ftpserver-core</artifactId><version>1.2.0</version></dependency> 2.yml文件添加Ftp服务参数 3.增加apache.ftpserver专用配置文件 文…