一次日语翻译的Chrome插件开发经历

序言

去年7月刚过了日语N2,想着今年考个N1,为了加深日语文化的了解,还有学习日语,平时免不了经常上日语网站。

但是毕竟水平有限,所以不免遇到不认识的单词,日语单词的一个特点就是很多单词你知道是什么意思,但是不知道怎么读。

比如:“簡素な構造” 中的第一个词:“簡素”,很显然就是“简单,朴素的意思”,但是你肯定不知道它的读音是:“[かんそ]①”。

以前遇到这样的词的时候,就会在沪江小D网页版上面查询,但是这样特别麻烦,你要跳转到别的网站,更别提沪江每次进去弹出来的广告了。

所以呢,就想找一个能不能弄一个划词翻译的Chrome插件,能够标注出读音,怎么详细怎么来,这样便于学习。

开始折腾

因为之前写过一些过滤广告和网页辅助排序的Chrome扩展,作为一个爱折腾的开发者。当然是自给自足风衣足食啦。

百度翻译API

首先想到的是,直接使用翻译API来进行翻译,然后把翻译的结果在当前页面加一个提示框显示出来。

首先是百度翻译API:http://api.fanyi.baidu.com/api/trans/product/apidoc 官方文档很简单,只需要注册一个appid,然后HTTP请求即可。百度翻译API每个月有200W字符的免费使用资源,所以可以随便用。

注册开发者账号,申请了appid,然后照着它的文档模拟请求,因为签名方式很简单,而且官方给出了demo,很轻松就获取到了返回数据。

然而,返回的数据格式有点不对。

{
    "from": "jp",
    "to": "zh",
    "trans_result": [
        {
            "src": "合格",
            "dst": "合格"
        }
    ]
}

。。。这翻译很直接,只有翻译结果,什么读音都没有,很绝望,PASS.

没办法,只能使用其他平台的API了,另外一个就是有道云的翻译API。和百度API一样,注册账号,创建应用。正打算使用的时候,发现有道云的翻译API是收费的,不过不用紧张,只要注册就送100元,够你翻译几千万字符了。

有道翻译API

有道云翻译API文档:http://ai.youdao.com/docs/doc-trans-api.s#p02,我发现一个有意思的事情,百度翻译API和有道云翻译API的调用方式几乎一模一样,两个平台给出的Demo中,什么数据签名方式,数据请求方式,几乎都是一样的。唯一需要注意的是,百度请求的应用标识参数是: appid,有道云的是: appKey,要小心一点。

很快,获取到百度翻译API的返回结果如下。

{
    "tSpeakUrl": "http:\/\/openapi.youdao.com\/ttsapi?q=%E5%90%88%E6%A0%BC&langType=zh-CHS&sign=3118EBCD5EC1D0A416EF32032FE55FAF&salt=1521012839301&voice=4&format=wav&appKey=3a72ad95fe43ac83",
    "query": "合格",
    "translation": [
        "合格"
    ],
    "errorCode": "0",
    "dict": {
        "url": "yddict:\/\/m.youdao.com\/dict?le=jap&q=%E5%90%88%E6%A0%BC"
    },
    "webdict": {
        "url": "http:\/\/m.youdao.com\/dict?le=jap&q=%E5%90%88%E6%A0%BC"
    },
    "l": "ja2zh-CHS",
    "speakUrl": "http:\/\/openapi.youdao.com\/ttsapi?q=%E5%90%88%E6%A0%BC&langType=ja&sign=3118EBCD5EC1D0A416EF32032FE55FAF&salt=1521012839301&voice=4&format=wav&appKey=3a72ad95fe43ac83"
}

相比于百度,有道云增加单词的读音地址这一项,但是还是没有标注出片假名。

另外还有Google翻译的API没有测试,不过这个时候得转化一下思路,先去Chrome扩展商店看看是否已经有类似的插件。

Chrome应用商店

首先在Chrome扩展应用商店搜索“划词翻译”,有一些评分很高的插件。

我装了几个,这些插件确实做的比较好,我需要的划词功能都有,能中文翻译英文,能中文翻译日文,有的还有读音。

但是有一点,这些插件都没有标注出我要查询的单词的片假名读音,这可不行。

对于使用习惯了沪江小D查词的我来说,作为学习需求,查询一个单词,应该有各种读音,各种释义,各种词性和用法还有例句。

我找了很多插件,结果都是没有,就在我快要放弃的时候,我试着搜索了一下“沪江”。

居然有一个沪江小D的插件,虽然只有一个评论,还是个中评。我还是下载安装了,结果各种疯狂报错,无法使用。

迂回:挣扎

事情终于又回到了起点,我习惯于使用沪江小D的翻译结果,那么可不可以在沪江小D的翻译中寻找答案呢?

分析沪江小D

沪江小D翻译页面:https://dict.hjenglish.com/jp/jc/簡素

我最开始的想法:打开调试窗口,然后观察它查询单词的Ajax请求,模拟查询。然而,沪江小D查询单词并不是使用Ajax异步查询结果,(失望,不能看到接口)。

很容易发现,沪江小D查单词就是请求一个页面,把单词拼接在类似于:https://dict.hjenglish.com/jp/jc/ 这样的页面后面,所以我就想在插件里面构造这个url,然后使用ajax请求获取HTML数据,解析HTML数据。然而这个页面是禁止跨域请求的,也就是说服务端设置了禁止跨域请求,option不通过。我又把这个页面放在一个 iframe 里面,然而

Refused to display 'https://dict.hjenglish.com/jp/jc/%E7%B0%A1%E7%B4%A0' in a frame because it set 'X-Frame-Options' to 'sameorigin'.

该网页禁止iframe访问。崩溃!

这个时候我猛然想到沪江小D不是有手机APP吗,那肯定会请求服务端API啊!所以我立马使用Fiddler抓包,我激动地输入单词,点击查询按钮,Fiddler中果然出现了查询请求。是压缩过的,解压之后是json格式的返回值,data里面是一大堆乱七八糟的鬼东西。呵呵哒,加密过的。

我这儿不得不佩服沪江做得真是好!

使用服务器转发

然而我是不会放弃的,不就是跨域的问题,你再怎么禁止跨域,也是浏览器能访问的嘛!对吧!在Postman中模拟请求不需要额外的cookie信息后。我在服务器写了一个转发器,请求页面数据,然后返回给插件客户端。由于我的服务器主要是PHP环境,所以用了PHP写的转发器。主要代码如下:

// 允许跨域>_<
header('Access-Control-Allow-Origin:*');
header('Access-Control-Allow-Methods:POST,GET');
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept");
header('Content-Type:application/json;charset=UTF-8');

class HujianTranslate
{

    protected $base_api = [
        'Japanese_Chinese' => 'https://dict.hjenglish.com/jp/jc/', // from Japanese to Chinese
        'Chinese_Japanese' => 'https://dict.hjenglish.com/jp/cj/', // from Chinese to Japanese
        'Chinese_English' => 'https://dict.hjenglish.com/w/',  // from Chinese to English
        'English_Chinese' => 'https://dict.hjenglish.com/w/',  // from English to Chinese
    ];

    // 根据查询语言转换url
    protected function buildUrl($query, $from, $to)
    {
        $url = $this->base_api[$from . '_' . $to];
        $url = $url . rawurlencode($query);
        return $url;
    }

    public function translate($query, $from, $to)
    {
        $url = $this->buildUrl($query, $from, $to);
        $response = $this->curlCall($url, '', 'GET');
        return $response;
    }

    // curl模拟发送http请求
    public function curlCall($url, $params = null, $method="post", $withCookie = false, $timeout = CURL_TIMEOUT, $headers=array())
    {
        $ch = curl_init();
        $data = '';
        $params && $data = http_build_query($params);
        if($method == "post" || $method == "POST")
        {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
            curl_setopt($ch, CURLOPT_POST, 1);
        }
        else
        {
            if ($data) {
                stripos($url, "?") > 0 ? $url .= "&$data" : $url .= "?$data";
            }
        }

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        // UserAgent 模拟
        $useragent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36';
        curl_setopt($ch, CURLOPT_USERAGENT, $useragent);

        // 绕过SSL认证
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);

        // 设置请求头部
        $headers && curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        // 设置cookie
        $withCookie && curl_setopt($ch, CURLOPT_COOKIEJAR, $_COOKIE);
        $response = curl_exec($ch);
        if (false === $response) {
            die(curl_error($ch));
        }
        curl_close($ch);
        return $response;
    }
}

$translator = new HujianTranslate();
echo $translator->translate($_GET['query'], $_GET['from'], $_GET['to']);
exit();

页面修改

终于能够成功获取了页面数据,但是这个时候我遇到了一个抉择,在哪儿解析

  • 在服务端解析好页面数据,然后把结果转发给插件客户端
  • 服务端不做任何数据处理,在客户端解析页面数据

开始我是想在服务端解析的,PHP的自带解析器用于XML,用来解析HTML的时候出现了一大堆报警,虽然可以正常跑,但是很不爽,又不太想重新用Python写一遍,而且服务器资源有限。后来就把这个解析的任务交给客户端了,毕竟JavaScript解析HTML很方便!

下面是对查询url:https://dict.hjenglish.com/jp/jc/上 的HTML内容解析函数

function parseHTML(data) {
    var parser = new DOMParser(),
            doc = parser.parseFromString(data, 'text/html'),
            details = [], items,
            word_details = doc.getElementsByClassName('word-details-pane');

    // 遍历每种解释含义
    for (var i = 0; i < word_details.length; i++) {
        details[i] = {};

        var word = word_details[i],
                detail = {},
                pronounces = word.getElementsByClassName('pronounces')[0],
                word_text = word.getElementsByClassName('word-text')[0],
                simple = word.getElementsByClassName('simple')[0],
                detail_group = word.getElementsByClassName('detail-groups')[0];

        // word text 解析
        detail.text = word_text.getElementsByTagName('h2')[0].innerHTML;

        // pronounces 发音解析
        detail.pronounce = {};
        items = pronounces.getElementsByTagName('span');
        detail.pronounce.kana = items[0].innerHTML;
        detail.pronounce.roma = items[1].innerHTML;
        detail.pronounce.accent = items[2].innerHTML;
        detail.pronounce.audio = items[3].getAttribute('data-src');

        // simple 词意解析
        detail.meaning = {};
        detail.meaning.pos = [];  // 词性
        detail.meaning.means = []; // 词义
        var poses = simple.getElementsByTagName('h2');
        for (var j = 0; j < poses.length; j++) {
            detail.meaning.pos[j] = poses[j].innerHTML;  // 词性
            // 词义li列表
            var lis = poses[i].nextSibling.nextSibling.getElementsByTagName('li');
            for (var k = 0; k < lis.length; k++) {
                detail.meaning.means[j] = [];
                detail.meaning.means[j][k] = lis[k].textContent; // 带序号的词义
            }
        }

        // 解析详细释义
        detail.meaning.detail = detail_group;
    }
    return detail;
}

弄好解析函数之后,犯难了,我该怎么展示这么多的数据呢?回到沪江小D的查询展示页面,最后的展示效果不也就和这个差不多嘛!

所以最后,我放弃了使用解析函数,而是直接截取页面中的翻译结果节点,然后重新调整了样式。

function parseResponse(data) {
    var parser = new DOMParser(),
            doc = parser.parseFromString(data, 'text/html'),
            content = doc.getElementsByClassName('word-details')[0];
    // content 为主要内容区域
    document.body.appendChild(content);
}

对内容的重新修改包括:

  • 调整间距,重新设置样式
  • 去掉广告内容
  • 因为一个词有多种读音,添加tab
  • 限制内容显示区域,隐藏进度条
  • 高亮标记例句中的查询单词
  • 完善音频播放

放一下音频播放器的代码:

// 音频播放器
function AudioPlayer() {
    var audio = document.createElement('audio');
    audio.setAttribute('controls', 'controls');
    audio.style.display = 'none';
    // audio.setAttribute('src', src);
    document.body.appendChild(audio);

    this.play = function(src) {
        audio.setAttribute('src', src);
        audio.play();
        return this;
    };
    this.stop = function() {
        audio.pause();
        return this;
    };
    // 播放结束回调
    this.end = function(callback) {
        var repeat = setInterval(function() {
            if (audio.ended) {
                clearInterval(repeat);
                callback && callback();
            }
        }, 100);
        setTimeout(function() {
            clearInterval(repeat);
        }, 5000);
    };

    return this;
}

最后如愿了,将沪江翻译的内容成功改装到一个小窗口中。接下来就是将内容封装到Chrome插件中就可以了。最后的效果如下:

可以下拉显示例句,点击小喇叭可以发音,点击开始的词条可以切换不同发音。
对比沪江小D页面可以点击这儿

最后:放弃

封装成插件之后,只能我自己用啊,毕竟版权是沪江的,那么可不可以联系沪江的官方,获得授权什么的呢?

所以我就联系了沪江的客服,了解了几个事情

  • 之前那个在Chrome上面的插件不是官方的
  • 我如果要共享插件代码的话,得和他们合作部门谈
  • 沪江已经在制定计划开发Chrome插件
  • 沪江的客服态度很好

而且,我还发现了一个很恶心的事情,在沪江翻译的页面里面,自带划词翻译,而且效果挺好的,标注了读音。效果图如下,双击单词会在单词下面出现翻译按钮,双击翻译会在小窗口显示读音和释义。这个过程是一个ajax请求,不过呢,需要appid和签名。

所以,我所做的工作根本没有什么用处。

所以我泪流满面的。。。久久不能言语。

相信沪江小D能够使用自己的API的话,开发的插件一定类似于这个小D划词释义一样,可以看假名。

完败收获

  • PHP 有相关的库可以像jQuery一样解析HTML,可用于爬虫
  • 服务端解析时,正则表达式可以用平衡组类似于堆栈匹配标签
  • CSS中使用counter等函数可以为li列表自动添加序号
  • 服务端跨域的破解方法是请求转发
  • 复习了一波CSS样式和布局
  • 我就是一个傻逼(私は馬鹿な人だ)

个人博客原文:不想说

原文地址:https://www.cnblogs.com/youyoui/p/8569362.html

时间: 2024-10-08 07:09:31

一次日语翻译的Chrome插件开发经历的相关文章

【Chrome】Chrome插件开发(一)插件的简单实现

不同浏览器插件开发比较 Chrome的插件开发起来最简单,总体上看没什么新的技术,开发语言就是javascript,web前端工程师能很快上手. Firefox的插件开发则复杂许多,涉及到环境的搭建和一些WEB以外的技术. IE的插件开发就更复杂了,需要熟悉C++和COM技术,当然还要装微软的Visual Studio. 这里有篇老外写的文章,对比Chrome.Opera和Firefox的插件开发的:http://blog.nparashuram.com/2011/10/writing-brow

vue.js 初体验— Chrome 插件开发实录

欢迎大家关注腾讯云技术社区-博客园官方主页,我们将持续在博客园为大家推荐技术精品文章哦~ 作者:陈纬杰 背景 对于经常和动画开发打交道的开发者对于Animate.css这个动画库不会陌生,它把一些常见的动画效果都封装起来了,非常实用.但是有时候在开发中,仅仅只是需要某一两个动画效果,把整个CSS文件都引入,这样不是太好. 需求就出现了,能不能有一个工具可以直接预览Animate.css对应的动画效果,并且生成对应的动画代码呢? 作为一个UI开发,平时跟Chrome浏览器打交道最多,于是就整了一个

Chrome插件开发入门(二)——消息传递机制

Chrome插件开发入门(二)——消息传递机制 由于插件的js运行环境有区别,所以消息传递机制是一个重要内容.阅读了很多博文,大家已经说得很清楚了,直接转一篇@姬小光 的博文,总结的挺好.后面附一个自己写过的demo,基本就对消息传递能够熟悉了. 在开发 Chrome 扩展时经常需要在页面之间进行通讯,比如 background 与 content script 之间,background 与 popup 之间等等,本文结合官方文档中的例子介绍了 chrome 扩展开发中消息传递的基本实现. 一

提醒我喝水chrome插件开发指南

起因 因为最近工作比较忙,经常忘记了喝水.作为一名前端开发人员,面对着浏览器工作是常态.所以这里为了解决这个痛点,面向前端开发人员写了一款浏览器插件.他的作用就是提醒喝水. 这里将半个小时设置为一个周期,大概和番茄工作法的原理一样.基本上集中注意力半个小时人也就累了.这个时候喝口水,舒缓一下紧张的神经.也作为一个休息的周期.为我们的工作继续高效的进行奠定了节奏. 成果 这是我做的浏览器插件 插件下载地址 开发思路 下面顺道介绍一下浏览器插件开发思路,编程不光要求理论还要有实践,撸起袖子直接干.

Chrome插件开发 小插件-acfun看图 1

之前在acfun看文章,经常遇到别人发其他网站的图而导致无法看到.这很不好,而且要想看到这些图片,操作是获得图片地址后,将最后的jpg改为jpeg即可,这种简单的操作应该是很容易实现的,于是我要开发一个简单的小插件来方便自己看评论.... 首先,先介绍一下开发的原因,在acfun下的评论中有时候会有一些其他人发的图片,这些图片由于是从自己的网盘或者空间发出来的,类似百度,会被屏蔽,出现 一般的解决方法是获得其图片地址后,在新的页面打开,将最后的jpg后缀改为jpeg,然后就可以看到图片了. 然后

chrome插件开发入门

1.参考文档链接 chrome浏览器插件开发官网教程 https://developer.chrome.com/extensions/getstarted foege工具,能够使用一份代码同时生成firfox,chrome,ie的插件,但是目前该项目已经不再维护 http://legacy-docs.trigger.io/en/v1.4

Chrome插件开发 小插件-acfun看图 3

插件的安装与使用. 写好插件后,通过chrome开发者模式加载插件. 点击加载扩展程序,选择插件的文件夹即可在chrome中加载插件. 然后更改自己的插件后点刷新进行更新,如果有错误,chrome会报错. 要想发布自己的插件就要先交钱,这跟steam上的绿光,以及ios应用等等都一样. 全部: ACfun看图

google chrome插件开发,自己动手,丰衣足食

因为平时上网都用chrome,但总感觉除了速度快,简洁以外总还有地方满足不了我的需要,然后找插件…后来发现,插件虽然海量但找个称心如意的也不是件容易的事儿,用在找插件的时间都能自己写一个了,于是,今年夏天开始的闲暇时间也写了几个,然后在应用中心断断续续发布了,这些插件原本是给自己用的,但也有几个用户专门找我提出了他们的需求. 从开始的不懂到现在三两下就能玩儿一个,走了点远路,今天在这里写下从开发到发布的简单流程,都是很简单的知识 一个可用的插件至少包括一个manifest.json和一个js文件

Chrome插件开发 小插件-acfun看图 2

继续,然后才是重点,关于右键菜单,右键菜单的创建使用chrome.contextMenus.create()函数.而要新建一个js文件来存放这些函数. 这些函数以及事件响应都是要在浏览器运行时调用,所以要在manifest中的 "background" 项中的   "scripts"中加上js项,表示这些是浏览器运行是的背景为这些js,即这些js一直在运行监听. "background": { "scripts": [&quo