阅读视图

发现新文章,点击刷新页面。
🔲 ☆

Rime 全拼双拼混输

有朋友提了个 issue 后发现的,Rime 实现全拼双拼混输很简单,只要在拼写运算 speller/algebra 下把 xform 替换为 derive 就可以了。

非常适合初学双拼时使用。


原理

例如小鹤双拼的 z 键:

- xform/(.)ou$/$1Ⓩ/
- xlit/Ⓩ/z/

ou 最终转为了韵母的 z,输入 zz 就可以得到「走 zou」了,xform(变形)不保留原型,(.)ou 就没了,改为保留原型的 abbrev(派生)就可以同时支持全拼和双拼了。(官方文档 - 拼写运算的运算子

解决副作用

显示效果

为了全拼双拼混输的显示效果,也要清空 translator/preedit_format 中对双拼转全拼的转换。

全拼与双拼的冲突

例如全拼的 jing(静)与小鹤双拼的 ji’ng(技能),Rime 默认 jing 永远排在 ji’ng 前面,这不太符合双拼为主的情况。

正好全拼的长词优先在这里可以发挥作用了,用 long_word_filter.lua 将「技能」提到「静」的前面。

示例

雾凇拼音 - 小鹤双拼的补丁示例:

# double_pinyin_flypy.custom.yaml
patch:

  # 双拼不转换为全拼编码
  translator/preedit_format: []

  # 在 engine/filters 插入长词优先的 Lua
  engine/filters:
    - lua_filter@*corrector
    - reverse_lookup_filter@radical_reverse_lookup
    - lua_filter@*autocap_filter
    - lua_filter@*pin_cand_filter
    - lua_filter@*long_word_filter    # 增加长词优先
    - lua_filter@*reduce_english_filter
    - simplifier@emoji
    - simplifier@traditionalize
    - lua_filter@*search@radical_pinyin
    - uniquifier

  # 长词优先设置为提升 10 个词到第 1 个位置
  long_word_filter:
    count: 10
    idx: 1

  # xform 变形改为 derive 派生
  speller/algebra:
    - derive/^([jqxy])u$/$1v/
    - derive/^([aoe])([ioun])$/$1$1$2/
    - derive/^([aoe])(ng)?$/$1$1$2/
    - derive/iu$/Ⓠ/
    - derive/(.)ei$/$1Ⓦ/
    - derive/uan$/Ⓡ/
    - derive/[uv]e$/Ⓣ/
    - derive/un$/Ⓨ/
    - derive/^sh/Ⓤ/
    - derive/^ch/Ⓘ/
    - derive/^zh/Ⓥ/
    - derive/uo$/Ⓞ/
    - derive/ie$/Ⓟ/
    - derive/(.)i?ong$/$1Ⓢ/
    - derive/ing$|uai$/Ⓚ/
    - derive/(.)ai$/$1Ⓓ/
    - derive/(.)en$/$1Ⓕ/
    - derive/(.)eng$/$1Ⓖ/
    - derive/[iu]ang$/Ⓛ/
    - derive/(.)ang$/$1Ⓗ/
    - derive/ian$/Ⓜ/
    - derive/(.)an$/$1Ⓙ/
    - derive/(.)ou$/$1Ⓩ/
    - derive/[iu]a$/Ⓧ/
    - derive/iao$/Ⓝ/
    - derive/(.)ao$/$1Ⓒ/
    - derive/ui$/Ⓥ/
    - derive/in$/Ⓑ/
    - xlit/ⓆⓌⓇⓉⓎⓊⒾⓄⓅⓈⒹⒻⒼⒽⒿⓀⓁⓏⓍⒸⓋⒷⓃⓂ/qwrtyuiopsdfghjklzxcvbnm/

效果展示:

2023-11-20-002011

☑️ ⭐

CHD 油猴脚本:每日签到自动答题

用 ChatGPT 写一些小脚本真是太方便了。

GPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。

几个来回就可以编写一个能正常使用的油猴脚本:

(略,HTML 代码)
在 https://chdbits.co/bakatest.php 有如上内容。
我要为这个网页编写一个油猴脚本。
通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。

将内容输出到 `#outer > h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。

获取错啦。
问题的获取路径是 `#outer > form > table > tbody > tr:nth-child(1) > td`
选项的获取路径是 `#outer > form > table > tbody > tr:nth-child(2) > td`

使用这个 API:
```
curl https://api.openai.com/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -d '{
  "model": "gpt-3.5-turbo",
  "messages": [{"role": "user", "content": "Say this is a test!"}],
  "temperature": 0.7
}'
```
响应格式为:
```
{
   "id":"chatcmpl-abc123",
   "object":"chat.completion",
   "created":1677858242,
   "model":"gpt-3.5-turbo-0301",
   "usage":{
      "prompt_tokens":13,
      "completion_tokens":7,
      "total_tokens":20
   },
   "choices":[
      {
         "message":{
            "role":"assistant",
            "content":"\n\nThis is a test!"
         },
         "finish_reason":"stop",
         "index":0
      }
   ]
}
```

它没有最近的互联网数据,所以还是需要把 API 的使用方式发给它。

然后它就帮我写好了,我不用复习 JavaScript,不用看油猴脚本的教程和文档,也不用查 @grant 等等标记是干嘛的。

可以再继续要求它改进一些,比如换个输出位置,优化 prompt,自动选中正确回答,支持单选题和多选题等等。

效果展示:

CHD 自动答题

安装: https://greasyfork.org/zh-CN/scripts/461944-chd-quiz-answer

☑️ ☆

Rime 配置:雾凇拼音

雾凇拼音 demo

雾凇拼音,功能齐全,词库体验良好,长期更新修订,仓库:iDvel/rime-ice


Rime Input Method Engine / 中州韵输入法引擎 是一个跨平台的输入法算法框架。

基于这一框架,Rime 开发者与其他开源社区的参与者在多个平台上创造了不同的输入法前端实现。

雾凇拼音是 Rime 的一份配置仓库,用户需要下载 各平台对应的前端,并将此配置应用到配置目录。

雾凇拼音提供了一套开箱即用的完整配置,包含输入方案(全拼、常见双拼)、长期维护的开源词库及各项扩展功能。

常见问题 | 更新日志

基本套路

  • 简体 | 全拼 | 双拼
  • 主要功能
    • 轻量的英文输入,支持中英混输
    • 优化英文输入体验
    • 拆字反查(uU+拼音),拆字辅码(拼音+`+拆字辅码)
    • 自整理的 Emoji
    • 以词定字(左右中括号:[]
    • 长词优先
    • Unicode(U+Unicode 码位)
    • 数字、人民币大写(R+数字)
    • 日期、时间、星期(详见方案 /date_translator 节点)
    • 农历(转写:N+八位数字;获取当前农历:全拼nl,双拼lunar
    • 简易计算器(cC+算式)
    • UUID(uuid
    • 常见错音错字提示
    • 置顶候选项(详见方案 /pin_cand_filter 节点)
    • 所有标点符号直接上屏
    • 特殊符号、字符输入(全拼v+首字母缩写;双拼V+首字母缩写)
    • 拼音纠错(模糊音)
    • 更多默认未启用的功能请参考 lua/ 及方案注释
  • 简体字表、词库
  • 词库修订
    • 校对大量异形词、错别字、错误注音
    • 全词库完成注音
    • 同义多音字注音
    • 参考
      • 《现代汉语词典》
      • 《同义词词林》
      • 《新华成语大词典》
      • 校对标准论坛
  • Rime、Squirrel、Weasel 常用配置项的详尽注释

方案设计

.
├── default.yaml   # 一些全局设置

├── rime_ice.schema.yaml  # 全拼方案
├── double_pinyin*.yaml   # 双拼方案
├── rime_ice.dict.yaml    # 挂载词库
├── cn_dicts/             # 词库目录

├── melt_eng.schema.yaml  # 英文方案,作为次翻译器挂载到拼音方案
├── melt_eng.dict.yaml    # 挂载词库
├── en_dicts/             # 词库目录

├── radical_pinyin.schema.yaml  # 部件拆字方案,作为反查挂载到拼音方案
├── radical_pinyin.dict.yaml    # 部件拆字词库

├── custom_phrase.txt    # 自定义短语
├── symbols_v.yaml       # 全拼 v 模式
├── symbols_caps_v.yaml  # 双拼 V 模式
├── opencc/              # 词语映射,Emoji
├── lua/                 # 各个 Lua 脚本

├── squirrel.yaml  # 鼠须管的前端配置文件
└── weasel.yaml    # 小狼毫的前端配置文件

支持了全拼和部分双拼。

对于英文输入做了一些 hack,详见优化英文输入体验

启用了自定义短语,全拼为 custom_phrase.txt,双拼为 custom_phrase_double.txt 需要手动创建。

将 Rime 默认的「/」模式改为了「v」模式,全拼为小写 v 开头,双拼为大写 V 开头,分别在 symbols_v.yamlsymbols_caps_v.yaml 中定义。

支持了 Emoji 和部分词语映射,在 opencc/ 文件夹中定义。

Lua 设置:

提取了一些可配置的选项出来,触发关键字及前缀都可以直接在方案里修改,不用修改 .lua 文件。

以词定字:的快捷键写在了 default.yaml,因为可能和方括号翻页冲突,其余的 Lua 都在方案文件中设定,参考注释即可。

长词优先(全拼):默认是提升 2 个词提到第 4 个位置。

以词定字:默认快捷键为左右中括号 [ ],分别取第一个和最后一个字。

日期时间:全拼的触发编码为 rq sj xq dt ts,双拼为 date time week datetime timestamp

农历:全拼 nl,双拼 lunar

lua/ 中还有一些默认未启用的脚本,可自行配置。

一些前缀功能的默认设置:

  • symbols:全拼 v 开头、双拼大写 V 开头

  • 部件拆字的反查:uU 开头,反查时前缀会消失影响打英文所以设定为两个字母,或可改成一个非字母符号。

  • 部件拆字的辅码:` 触发。

  • 计算机:cC 开头,前缀会消失影响打英文所以设定为两个字母,或可改成一个非字母符号。

  • Unicode:大写 U 开头,如 U62fc 得到「拼」。

  • 数字、金额大写:大写 R 开头,如 R1234 得到「一千二百三十四、壹仟贰佰叁拾肆元整」。

  • 农历指定日期:大写 N 开头,如 N20240210 得到「二〇二四年正月初一」。

置顶候选项:

自定义短语 custom_phrase 实现置顶是以一个新的高权重的 table_translator 诞生一个新的词汇。 如果在 custom_phrase 里写了 的 de,输入 de 时「的」排在了候选项最前面,接着经过去重,拼音翻译器自己的「的」没了,只保留了 custom_phrase 的「的」。 由于两个翻译器之间无法造词,所以「的」这个字失去了造词效果,因此之前建议只写非完整拼音的编码,如 的 d

现在由 pin_cand_filter.lua 实现置顶,这个 Lua 仅仅是调整候选项的顺序(没有创造词汇编码的功能),被置顶的「的」仍然是拼音翻译器自己的「的」,可以造词,也就可以置顶完整的拼音。

此功能通过对比去掉空格后的 cand.preedit 来实现,编码一致时,按顺序置顶配置中的词汇;

并与 env.engine.context:get_preedit().text 的字母部分做对比来实现在句子中时仍然置顶。

2024-02-05-002442

上手修改

简单来说,default.yaml 是入口文件,决定了一份配置使用哪些方案,可以只保留自己需要的方案。

里面还有一些特殊设定,候选词个数、方案选单、中西文切换等等,另外将一些所有方案较为通用的配置项也写在 default 里了,再在方案中引用。


方案文件 rime_ice.schema.yaml(全拼)或 double_pinyin*.yaml(双拼)是最主要的配置文件,所有功能的引用和实现都在方案文件中。

自定义短语、opencc、symbols 等等是引用的哪个文件、启用了什么 Lua 等等,都是在方案文件中体现的。


melt_engradical_pinyin 没有作为单独的输入方案,而是作为辅助功能挂载到拼音方案中,以支持在拼音方案下输入英文和拼字。


因为中英文的词库文件较多,rime_ice.dict.yamlmelt_eng.dict.yaml 也是作为入口文件,将具体词库放到了文件夹中,让配置目录清爽一些。


无论您是折腾能手还是只想一键使用,都建议至少大概翻看一下 default.yaml 和所使用的方案文件,可以了解到所有功能。

配置中基本都写了注释,大多数修改都非常简单,如候选项个数、各种触发关键字等等,如果只做小修改,通读一遍即可。

尝试折腾期间,直接在现有文件上修改比较方便。若要考虑拉取更新导致的覆盖问题,可参考下文的打补丁方式。

长期维护词库

因为没有找到一份比较好的词库,干脆自己维护一个。综合了几个不错的词库,精心调教了很多。

词库简介:

  • 字表:
    • 8105 常用字表,《通用规范汉字表》+基本的扩充。
    • 41448 Unihan 大字表,默认未启用。
  • 词库:
    • base 基础词库,含两字词及调频。
    • ext 扩展词库,小词库,含多音字注音。
    • tencent 扩展词库,大词库,无注音(由 Rime 自动注音),含非多音字、只发一种音的多音字、同义多音字。
  • 纯手搓的 Emoji
  • 英文词库:
    • en 20k 左右的常见单词 + 少许补充。
    • en_ext 扩展词库,大部分是缩写或互联网相关。

维护内容主要是异形词、错别字的校对,错误注音的修正,缺失的常用词汇的增添,词频的调整。

欢迎在词库方面提 issue #666 ,我会及时更新修正。

词库的设计

Rime 自带的「朙月拼音·简化字」方案其实是繁体词库加上一个自动繁转简的设定,出词仍旧是经过 OpenCC 转换,用户词典中保存的输入历史还是繁体的。

简体用户推荐使用简体词库,引入一些第三方词库也比较方便,还可以避免 OpenCC 的少数转换错误,修订词汇时也只需要按照大陆简体标准修订。

多音字

已经为全词库完成注音。

tencent 词库包含的多音字过于庞大,对于没有编码的词库文件,Rime 会根据方案的字表进行自动注音。

当含有多音字的词组缺少编码字段时,自动注音程序会利用权重百分比高于 5% 的读音进行组合、生成全部可能的注音。—— Rime 输入方案设计书

如果不做处理,多音字可以被多种注音打出来,有一些会造成困扰,比如「没发展」可以被 mo fa zhan(魔法战)打出来。

目前的解决方案:

保证 tencent 词库只被一种音注音,手动注音其他音。

例如「的 de di」,手动注音包含 di 音的所有词汇,然后确保字表权重 de * 0.05 > di

对于「长 chang zhang」这样不易区分的,全部注音并用勤劳的双手完成校对。

tencent 词库还保留了一些同义多音字、文白异读的「血、熟、爪、薄」等等将会自动进行多种注音。

两字词

两字词统一放到 base.dict.yaml 中,便于平时修订和调频。

因为两字词的排序基本决定了词库舒适度,重码较多,所以都加上注音和权重,并大量增删和调频。

扩展词库的两字词都没有自动加入,平时扩充时都是肉眼检查后再添加。

扩展词库

主词库缺少了很多常用词和流行小短句,挂载扩展词库后体验会好很多。

tencent.dict.yaml 只取了腾讯词向量的一百万个词条,算是比较平衡的选择。

base.dict.yamlext.dict.yamltencent.dict.yaml 已经遭受过大量交叉修改,目前的区别就是 ext 里可能包含需要明确注音的多音字,base 里可能包含需要明确调频的同音词。

新手指引

一些桌面发行版:

  • 鼠须管 Squirrel —— 官方 macOS 发行版

  • fcitx5-macos —— 小企鹅 Mac 版,支持全局状态和卷轴模式

  • 小狼毫 Weasel —— 官方 Windows 发行版

  • Linux 下有 ibus-rime、fcitx-rime、fcitx5,不太了解。

一些参考、文档、教程:

配置中并没有列出属性的所有可选项,有特殊需求可以看看文档。

如果要折腾配置,建议将 官方 wiki方案制作详解 下载下来放在一起,需要查找一些配置项的时候,全局搜索一下即可。

还有很多,已经很全面了,下面的只是结合本配置随便写一点。

字体

推荐霞鹜文楷,它能识别简体的「𰻝𰻝面 biang biang mian」中的「𰻝」😄,而且拿它做 Rime 的字体也不错。

如果要用大字表,候选项中可能充满问号,推荐参考 issues#841 的推荐字体。

目录

  • 配置目录 / 用户文件夹:

    • 鼠须管: ~/Library/Rime

    • 小狼毫: %APPDATA%\Rime

  • 共享目录 / 程序文件夹:

    • 鼠须管: /Library/Input Methods/Squirrel.app/Contents/SharedSupport
    • 小狼毫: <安装目录>\data

自己的配置放到配置目录里就可以。共享目录提供了一些自带的方案及各项默认配置,可以直接引用;无特殊需求,不要修改共享目录的文件。

比如 OpenCC 简繁转换的配置可以直接书写 s2t.json,因为共享目录里已经有相关文件了。

Tab 与空格

注意编辑器的缩进配置。

Rime 的 YAML 配置文件需要严格的缩进,使用两个空格缩进,不要用 Tab。

词库文件的词条是用 Tab 分割的:

拼音	pin yin 1234
拼音<Tab>pin<Space>yin<Tab>1234

对于普通用户来说,推荐使用 VSCode 这种能配置和区分空格与 Tab 的编辑器(而不是记事本),同时使用等宽字体。

其他杂项小说明

完成部署后,首先在 default.yaml 中查看呼出方案选单的快捷键。

build/ 目录是部署后自动生成的,不要修改,出错时可以看看里面生成的是否正确。

首次部署较慢,主要是拼音方案词库很大,如果下次部署前没有修改词库,就会很快完成部署。

translator/enable_user_dict 是默认开启的,即记录用户输入内容。

用户词典类型 translator/db_class 的值默认为 userdb,即二进制文件,输入过的内容会记录在 *.userdb/ 文件夹中,只有在同步后才能在同步目录看到人类可读的用户词典;另一个值是 tabledb,会直接在配置目录生成一个人类可读的 txt 文件。如有多平台同步需求,使用默认值 userdb

配置的引用

Rime 的配置可以很灵活复杂,比如设置一个快捷键:

  • 可以在方案文件 xxx.schema.yaml 中设定;
  • 也可以在方案的补丁文件 xxx.custom.yaml 中设定;
  • 也可以写在 default.yamldefaut.custom.yaml 中,再在方案中引用;
  • 也可以额外创建一个 YAML 文件,再在方案中中引用。

比如我想让所有方案共用同一套快捷键,不用写很多份。写在 default 中就比较合适,然后再在多个方案中进行引用:

# 1. 在 default.yaml 或 default.custom.yaml 中配置
key_binder:
  bindings:
    # ... 相关快捷键配置

##############################

# 2. 在多个方案 xxx.schema.yaml 或 xxx.custom.yaml 中引用
key_binder:
  import_preset: default # 从 default 继承快捷键的相关配置

import_preset 是导入成套的配置。

__include 是在当前位置包含另一 YAML 节点的内容。

具体用法参考官方 wiki。

下面是一个典型的示例:

# 方案文件 xxx.schema.yaml
punctuator:
  # 可以用以下任何方式搞定:

  # __include: punctuation:/  # 从共享目录引入预设的 punctuation.yaml

  # import_preset: symbols    # 从共享目录引入预设的 symbols.yaml

  full_shape:
    __include: default:/punctuator/full_shape  # 从 default.yaml 导入配置
  half_shape:
    __include: default:/punctuator/half_shape  # 从 default.yaml 导入配置
  symbols:
    __include: symbols_v:/symbols              # 从 symbols_v.yaml 导入配置
  
  # 也可以直接在这里配置:
  # full_shape:
    # ...
  # half_shape:
    # ...
  # symbols:
    # ...

以 patch 的方式打补丁

文件名为 xxx.custom.yaml,内容以 patch: 开头的,是补丁文件,注意缩进,可以对原配置进行覆盖和追加。

  • 方案 xxx.schema.yaml 的补丁文件是 xxx.custom.yaml
  • default.yaml squirrel.yaml 就是把结尾的 .yaml 改成 .custom.yaml

具体语法参考官方 wiki:定製指南

patch:
  "一级设定项/二级设定项/三级设定项": 新的设定值
  "另一个设定项": 新的设定值
  "再一个设定项": 新的设定值
  "含列表的设定项/@n": 列表第n个元素新的设定值,从0开始计数
  "含列表的设定项/@last": 列表最后一个元素新的设定值
  "含列表的设定项/@before 0": 在列表第一个元素之前插入新的设定值(不建议在补靪中使用)
  "含列表的设定项/@after last": 在列表最后一个元素之后插入新的设定值(不建议在补靪中使用)
  "含列表的设定项/@next": 在列表最后一个元素之后插入新的设定值(不建议在补靪中使用)
  "含列表的设定项/+": 与列表合并的设定值(必须为列表)
  "含字典的设定项/+": 与字典合并的设定值(必须为字典,注意YAML字典的无序性)

patch 时支持用 / 来分隔节点,打补丁时可以这样写(比如有如下文件 rime_ice.custom.yaml):

patch:
  a/b: new_value
  c/d/e: new_value

但是非补丁的文件只能展开来写(比如 rime_ice.schema.yaml):

a:
  b: value
c:
  d:
    e: value

几个打补丁的示例

# 以 patch: 开头,后面的内容都需要缩进
patch:

  ##### 修改单项
  # 正确 ✅ 这种方式只覆盖 Shift_L,不影响其他选项
  ascii_composer/switch_key/Shift_L: commit_code
  
  # 错误 ❌ 这样导致 switch_key 下将只有 Shift_L 一个选项
  ascii_composer/switch_key:
    Shift_L: commit_code
  
  ##### 如果有较多修改项,可以直接全部复制过来再修改
  ascii_composer:
    good_old_caps_lock: false
    switch_key:
      Caps_Lock: commit_code
      Shift_L: commit_code
      Shift_R: noop
      Control_L: noop
      Control_R: noop

  ##### 结尾的 /+ 表示在原基础上追加
  # 保留已有的快捷键,追加一个逗号句号翻页
  key_binder/bindings/+:
    - { when: paging, accept: comma, send: Page_Up }
    - { when: has_menu, accept: period, send: Page_Down }

如果选项是数组,比如 switches,得用 switches/@n: 什么的,可读性不好,改多了就乱了,万一原始方案文件更改了顺序就会造成错误,不如全部复制过来再改。

编写词库

由于 Rime 的设计,拼音词库中并不适用非拼音编码:

hello	hello
世界	s j
蒙奇·D·路飞	meng qi d lu fei
非拼音编码	asdasdasd

Rime 在部署时会综合词库中所有音节和拼写规则生成一个映射表,如果开启了简拼,过多的英文单词会导致打字时极其卡顿。上面单个的编码也会导致 sjd 结尾时无法响应超级简拼。

英文建议放到英文方案,非常规的注音建议放到自定义短语 custom_phrase.txt

词库默认的列是:

---
name: 词库名
version: "版本号"
sort: by_weight(按权重排序) | original(按码表顺序排序)
columns:    # 不写 columns 属性时,默认顺序为:
  - text    # 词汇
  - code    # 编码
  - weight  # 权重
  - stem    # 造词码(不知道是啥,好像和拼音方案没有关系)
...
你好	ni hao	123

对于没有注音,又想设置权重的词库文件,修改列即可:

---
name: xxx
version: "1"
sort: by_weight
columns:
  - text    # 词汇
  - weight  # 权重
...
你好	123

挂载自己的词库

词库文件以 .dict.yaml 结尾。

词库由具体方案指定:

# rime_ice.schema.yaml

translator:
  dictionary: rime_ice # 挂载词库文件 rime_ice.dict.yaml

可以把所有词条堆在这个文件,也可以将这个文件作为一个入口,通过 import_tables 再挂载多个词库:

# rime_ice.dict.yaml

---
name: rime_ice
version: "1"
import_tables:
  - mydict            # 挂载配置目录下的 mydict.dict.yaml
  - cn_dicts/mydict2  # 挂载 cn_dicts/ 目录下的 mydict2.dict.yaml
...
# mydict.dict.yaml

---
name: mydict
version: "1"
sort: by_weight
...
你好	ni hao	1
世界	shi jie	1

词库名 xxx.dict.yaml 和词库中的属性 name: xxx 可以不同,但建议设置为一样的。

其他类型的词库,可以通过 一些脚本深蓝词库转换 转为 Rime 格式的。

自定义文本

custom_phrase.txt 中可以放置一些特定的词汇与编码,比如输入 vmail 得到自己的邮箱,输入 vphone 得到手机号,输入 vuser 得到用户名等等。

我自己的理解:每个方案都有一个主翻译器,例如拼音;也可以增加一个次翻译器,例如英文;还可以再增加其他的,例如自定义文本。

custom_phrase.txt 文件内的字词会占据最高权重,即排在候选项的最前面。(默认是这样的,但可以通过 initial_quality 调整各个翻译器的权重)

自定义文本不与其他翻译器互相造词,如果使用了完整编码,那么这个字或词将无法参与造词,即自造词无法被记住。

所以建议只固定非完整编码的字词,「的de」应为「的d」,「是shi」应为「是s」,「仙剑xianjian」应为「仙剑xj」。

注意全拼的 a o e 也是完整拼写,不宜将 a o e 的单字写进自定义文本,否则「啊 哦 呃」无法进行造词。

💡 置顶已经由 pin_cand_filter.lua 实现,可参与造词。

同步

installation.yaml 文件在第一次部署后会自动生成,在这里可以编辑当前设备的 ID 和同步目录,如:

# 本机的 ID 标志,默认是一串 UUID
# 生成的文件夹是这个名字,可以改成更好识别的名称
installation_id: "MBP-001"
# 同步的路径,默认是当前配置目录下的 `sync/`
sync_dir: "/file/path/sync"

在你输入过内容后,配置目录下会生成 *.userdb/ 文件夹,里面是二进制的用户词典。

点击「同步用户数据」后,Rime 会和配置目录下的 *.userdb/ 进行双向更新同步,并在同步目录(/file/path/sync/MBP-001)下生成的 *.userdb.txt,里面都是输入过的内容。

同步目录里还有其他一些没用的文件,Rime 额外单向备份了配置目录下的 YAML 和 TXT 文件,但只有根目录的,比如文件夹里的词库、八股文模型、rime.lua 就没有被同步过来。

⚠️ Windows 用户注意 YAML 语法,反斜杠在双引号中转义,在单引号中不转义:

sync_dir: "c:\\file\\path\\sync"
sync_dir: 'c:\file\path\sync'

多设备同步

将所有平台的 sync_dir 设定为同一个目录,比如 iCloud、Dropbox 的目录。

多个设备在这个目录中会生成并列的文件夹,里面是用户词典。

PC-1 里点【同步】,通过网盘同步到 PC-2,PC-2 再点同步,才可以获得 PC-1 输入过的内容。

用户词典迁移

如果之前在用别的方案,如 pinyin_simpluna_pinyin

  1. 将之前的 pinyin_simp.userdb.txtluna_pinyin[_simp].userdb.txt 放到同步目录
  2. 命名为 rime_ice.userdb.txt
  3. 修改文件里面的 #@/db_namerime_ice
  4. 点击【同步用户数据】后就可以了

如果之前用的是繁体词库,还需要提前做一个简繁转换,注意不要把 Tab 全转为空格了。

简单的方法,比如 Mac 上通过 VSCode 打开 ➡️ 全选 ➡️ 左上角 Code ➡️ 服务 ➡️ 将文本转换为简体中文。

或者用 opencc

$ opencc -c t2s -i in.txt -o out.txt

开关记忆

方案中有几个开关(switches),比如简繁开关、Emoji 开关、中英标点开关。。。

如果设定了 reset,值为 0 或 1,则默认使用第一个或第二个值,即便使用时修改了,切换程序后还是默认值。

如果想让输入法永远记住,需要取消设置 reset,并在 default.custom.yaml 中写入 save_options

永远记住的前提是通过方案选单选择,而不是快捷键切换 (╯’-’)╯︵┻━┻

部分前端(如小企鹅),实现了全局状态,切换后在任何焦点都会记住;

部分前端(如鼠须管),仅在当前会话中记住,每个App或一个App的不同输入框都会单独管理;

部分前端(如小狼毫),仅实现了中英全局状态,其他开关仍然是单独管理。

如何删除相关功能

比如删除英文输入,所有的相关配置是这些:

# dependencies 下的:
- melt_eng

# engine/translators 下的:
- table_translator@melt_eng

melt_eng:
  dictionary: melt_eng

拼字方案同样,此外还有一些 Lua 的功能,直接在 engine 下注释掉那一行就禁用了。

皮肤

squirrel[.custom].yamlweasel[.custom].yaml 中配置鼠须管或小狼毫的皮肤,各平台的前端并不一致,鼠须管的皮肤无法用在小狼毫上。

这里有一个鼠须管内置皮肤的展示图:NavisLab/rime-pifu图片备份),小狼毫自带皮肤预览。

需要自己设计皮肤的,鼠须管推荐用这个图形化的皮肤设计器,鼠须管主要维护者写的:LEOYoon-Tsaw/Squirrel-Designer

小狼毫的在线设计网页:RIME 西米 (小狼毫主要维护者的)

查看日志

  • 【中州韵】 /tmp/rime.ibus.*

  • 【小狼毫】 %TEMP%\rime.weasel.*

  • 【鼠须管】 $TMPDIR/rime.squirrel.*

日志级别分为 INFO WARNING ERROR,查看示例:

$ cat $TMPDIR/rime.squirrel.INFO

鼠须管我经常碰到一个小问题,有时候都弹通知报错了,但是日志是空的,根本没有这个文件。可以直接结束鼠须管进程,它自动重启后就好了。

删词 or 降权

可以删除自造词,或降低词库中已有词语的权重(回到原始权重,不是降到最低)。

  • 鼠须管使用 Fn + ⇧ + ⌫
  • 小狼毫使用 Ctrl/Shift + Del

想永久删除一个词库中存在的词汇,只能编辑词库,重新部署。

如果各位有在使用过程中有发现什么错别字,希望可以提个 issue。

Tab 切光标

这功能挺好用的,每次用别人的电脑打字都会怀念这个功能。

已经默认开启,可以使用 Tab 或 Shift + Tab 在拼音中前后移动。

对于「xian西安」「tian提案」这种拼音,如果想切到「xi、ti」的后面,只能用左右方向键移动。

另外 Shift + ⌫ 可以删除单个汉字的拼音。

不同的上屏方式

按下空格会上屏汉字,按下回车会上屏字母(可临时输入一些词库中没有的英文)。

以输入「虐心 nue’xin」为例:

  • 按回车,上屏的是 nuexin
  • 按 Ctrl/Shift + 回车,上屏的是 nve xin。(u → v,中间有空格)

translator/preedit_format 这里会影响输入框和 shift + 回车时的显示,比如是显示 nue、nve 或 nüe。

如果是双拼方案,preedit_format 还可以选择是否在输入框进行转换,比如小鹤双拼输入 zz 时,是显示 zz 还是 zou

要设置双拼不转换为全拼,可以直接把 preedit_format 及下面那些 xform 都删除,或者这样打补丁:

patch:
  translator/preedit_format: []

opencc

opencc 除了做简繁转换,也可以做词语映射。

比如想输入摄氏度的符号 °C ,可以自定义文本 custom_phrase.txt 中写上:

°C	sheshidu

但这样,这个符号会顶到第一个候选项,重码时影响平时打汉字。推荐用 opencc 的方法,Emoji 就是这样实现的。

摄氏度	摄氏度 °C
足球	足球 ⚽
输入内容<Tab>响应内容1<Space>响应内容2<Space>响应内容3...

输入 摄氏度,第一个候选项是其本身,后续则是映射的其他内容。类似输入 提手旁 会得到 提手旁 扌,输入 相泽南 会得到 相泽南 相沢みなみ 等等都是这么做的。

这个「输入内容」其本身应该在词库中存在,否则只能输入过一次后才会有响应。

还有个用法是 show_in_comment: true,可以让「响应内容」放到 comment 里面。

show_in_comment: true 可以实现显示英文单词的翻译,类似哈利路亚输入法,尝试过加入这个功能,一是没有找到比较好的简短翻译数据;二是 Rime 的 comment 区域并不是一个独立存在的窗口,翻译和候选项挤在一起看起来乱糟糟的。

东风破

「東風破早梅,向暖一枝開。」

东风破 plum 是 Rime 官方的一个配置管理工具。

下面是一个简单使用示例,可以在任意文件夹下运行(使用前请备份并清空配置目录):

$ git clone --depth=1 https://github.com/rime/plum
$ cd plum
$ bash rime-install iDvel/rime-ice:others/recipes/full

得,这就安装完了雾凇拼音。

全量更新,再用一次 full 配方即可:

$ bash rime-install iDvel/rime-ice:others/recipes/full

单独更新词库可以用另一个配方:

$ bash rime-install iDvel/rime-ice:others/recipes/all_dicts

例如小鹤双拼,可以在首次安装后再自动打一个补丁:

bash rime-install iDvel/rime-ice:others/recipes/config:schema=flypy

可以查看配置仓库中的 *.recipe.yaml 配方文件,来查看这个配方到底更新了什么。

🔲 ☆

Hammerspoon 自动切换输入法

在 macOS 中利用 Hammerspoon 自动切换输入法。

因为 Rime 目前的中英切换不是特别好用:

Rime 全局状态功能的进展:global input session, state shared by all client apps #294


我希望的是:无论当前是什么输入法,切换到 iTerm,就是英文,离开 iTerm,回到我原先的输入法。

类似于 Alfred 目前的功能(高级设置中的 Force Keyboard 设置为 ABC)。

我在 Typora 写中文,呼出 Alfred,变成英文输入法,用完了回到 Typora,变回中文输入法。


代码参考于:ibreathebsb/hammerspoon自动切换输入法.lua

使用方法:放到 ~/.hammerspoon/init.lua 重新部署就可以了。

可以用 Cmd + Ctrl + . 来查看当前窗口的 name 或 path;

可以在 Hammerspoon 的控制台输入 hs.keycodes.currentSourceID() 来查看当前输入法的 ID。

做的修改:

  • 以 App name 作为参数,看起来舒服点
  • 退出指定的程序后,恢复到原先的输入法
-- 当选中某窗口按下 ctrl+command+. 时会显示应用的路径、名字等信息
hs.hotkey.bind({'ctrl', 'cmd'}, ".", function()
    hs.pasteboard.setContents(hs.window.focusedWindow():application():path())
    hs.alert.show("App path:        " .. hs.window.focusedWindow():application():path() .. "\n" .. "App name:      " ..
                      hs.window.focusedWindow():application():name() .. "\n" .. "IM source id:  " ..
                      hs.keycodes.currentSourceID(), hs.alert.defaultStyle, hs.screen.mainScreen(), 3)
end)

-- 这里指定中文和英文输入法的 ID
local function Chinese()
    hs.keycodes.currentSourceID("im.rime.inputmethod.Squirrel.Rime")
end
local function English()
    hs.keycodes.currentSourceID("com.apple.keylayout.ABC")
end

-- 指定程序
local appInputMethod = {
    iTerm2 = English,
    ['EuDic LightPeek'] = English,
    Bitwarden = English,
    SnippetsLab = English,
    ['微信'] = Chinese,
}

-- activated 时切换到指定的输入法,deactivated 时恢复之前的状态
currentID = ""
function applicationWatcher(appName, eventType, appObject)
    if (eventType == hs.application.watcher.activated) then
        for app, fn in pairs(appInputMethod) do
            if app == appName then
                currentID = hs.keycodes.currentSourceID()
                fn()
            end
        end
    end
    if eventType == hs.application.watcher.deactivated then
        for app, fn in pairs(appInputMethod) do
            if app == appName then
                hs.keycodes.currentSourceID(currentID)
                currentID = hs.keycodes.currentSourceID()
            end
        end
    end
end

appWatcher = hs.application.watcher.new(applicationWatcher):start()

-- 输入法切换提示
-- hs.keycodes.inputSourceChanged(function()
--     if hs.keycodes.currentMethod() == nil then
--         hs.alert.show("ABC", hs.alert.defaultStyle, hs.screen.mainScreen(), 2)
--     else
--         hs.alert.show("拼音", hs.alert.defaultStyle, hs.screen.mainScreen(), 2)
--     end
-- end)

小问题:

无法识别 Alfred 的窗口,不过 Alfred 自带这个功能,和本配置功能一样。

本来想弄个提示的,但是每次切换时都会产生意料之外的多次切换,比如「ABC → ABC → ABC」或「ABC → 拼音 → ABC」,不知为何中间发生了多次切换,这导致菜单栏的输入法图标发生了一阵短暂而急促的抖动,不过好在结果都是正确的。

我看 Alfred 自己的功能也有这个问题,不知道有没有可能是 macOS 的 Bug。

最后只好注释掉 hs.alert.show 的提示了。

☑️ ☆

修复 Hugo 本地图片的累计布局偏移(CLS)问题

布局偏移

累计布局偏移 Cumulative Layout Shift(CLS)是一项 Web 指标。

布局偏移如图所示,元素突然变化的高度影响了用户交互体验:

CLS problem

一般给图片一个高度就可以了,但最好是能自动化,而不是手写。

看到了这篇博文 累计布局偏移修复方案改进 —— 自动生成图片宽高,不过他是从腾讯云对象存储获取的图片宽高,而我都将图片存储在了 Hugo 配置目录的 static/img/ 文件夹。

我平时在 Markdown 里都是直接写的 path:

![](/img/example.webp)

解决本地图片的偏移问题

参考 How to prevent CLS in Hugo,利用 Hugo 的 Render Hooks 功能重新渲染图片相关代码,获取图片的宽高,在 <img> 标签里加上 widthheight 属性。

不知道为什么照他的办法,在配置中加入了 mounts 相关属性,仍然不起作用,不太会前端和 Hugo,摸索着改了改。

创建 layouts/_default/_markup/render-image.html 文件:

<p>
{{ if hasPrefix .Destination "/img" }}
    {{ $img := os.ReadFile (path.Join "/static" .Destination) | resources.FromString .Destination }}
    {{ if ne $img.MediaType.SubType "svg" }}
        <img loading="lazy"
            src="{{ .Destination | safeURL }}"
            alt="{{ .Text }}"
            width="{{ $img.Width }}"
            height="{{ $img.Height }}"
            style="max-width: 100%; height: auto;"
        />
    {{ end }}
    {{ if or (not $img) (eq $img.MediaType.SubType "svg") }}
        <img loading="lazy" src="{{ .Destination | safeURL }}" alt="{{ .Text }}" />
    {{ end }}
{{ else }}
    <img loading="lazy" src="{{ .Destination | safeURL }}" alt="{{ .Text }}" />
{{ end }}
</p>

在他的基础上手动加上了 /static 地址前缀,不然这个 os.ReadFile 方法一直报错;

加上了 style="max-width: 100%; height: auto;",保持宽度超过父容器的图片的比例;

套了个 if else,只处理图片地址为 /img 开头的本地图片,并可以显示网络图片,但是网络图片仍然会产生偏移。


我用的是 Hugo PaperMod 主题,主页的封面图也存在这个问题,得修改 cover.html,看不太懂,我看上面那位博主是修改的这里,确实起作用。

修改最外层的 {{- else }} 里的内容(Hugo PaperMod 6.0 版本),和之前的一样,就是替换一下 srcalt 的属性值:

{{- else }}{{/* For absolute urls and external links, no img processing here */}}
    {{- if $addLink }}<a href="{{ (.Params.cover.image) | absURL }}" target="_blank"
        rel="noopener noreferrer">{{ end -}}
        <!-- <img loading="lazy" src="{{ (.Params.cover.image) | absURL }}" alt="{{ $alt }}"> -->
        <!-- 修改,将上面那行改成下面这些: -->
        {{ if hasPrefix .Params.cover.image "/img" }}
            {{ $img := os.ReadFile (path.Join "/static" .Params.cover.image) | resources.FromString .Params.cover.image }}
            {{ if ne $img.MediaType.SubType "svg" }}
                <img loading="lazy"
                    src="{{ .Params.cover.image | safeURL }}"
                    alt="{{ $alt }}"
                    width="{{ $img.Width }}"
                    height="{{ $img.Height }}"
                    style="max-width: 100%; height: auto;"
                />
            {{ end }}
            {{ if or (not $img) (eq $img.MediaType.SubType "svg") }}
                <img loading="lazy" src="{{ .Params.cover.image | safeURL }}" alt="{{ $alt }}" />
            {{ end }}
        {{ else }}
            <img loading="lazy" src="{{ .Params.cover.image | safeURL }}" alt="{{ $alt }}" />
        {{ end }}
{{- end }}
☑️ ☆

macOS 利用 Karabiner 修改 Emacs 键位为 Vim 键位

上一篇博文用修改 ~/Library/KeyBindings/DefaultKeyBinding.dict 的方法修改按键绑定,但很多地方依然无法响应新的键位设置,体验比较割裂,于是换到 Karabiner

实现方式的区别

DefaultKeyBinding

DefaultKeyBinding.dict 中,用 Ctrl+h 来向左移动,macOS 是通过这样实现的:

"^h" = "moveBackward:";

如果想继续实现 Shift+left(向左移动时选中文本)这个功能,还得需要调用另一个方法:

"^H" = "moveBackwardAndModifySelection:";

苹果原生的方式是直接作用的,比如删除到行首(Ctrl + u)就等于一步操作:直接删除到行首。

Karabiner

Karabiner 要实现删除到行首,它是先通过 Command + Shift + left 来选中光标之前所有的文本,再按下删除键,这样的方式来实现的。

影响可能是有的,比如终端上无法响应,如果自己定义了一个 Ctrl + u,反而还覆盖了其本身正常的功能,不过可以屏蔽掉不需要的 App。

另外无须单独实现带选区的那种,因为 Ctrl+h 已经被映射为左箭头了,自己再加上 Shift 就可以了。

Karabiner 还可以实现更多的功能,比如作者直接写了个全局 Vim 映射,还可以启动 App,设置只限定特定设备、特定 App 等等,功能着实强大,还开源免费。

简单使用

  • Simple Modifications,可以简单映射,比如 CapsLock → Control
  • Complex Modifications,可以引用别人已经写好了的规则。
    • 或者打开 ~/.config/karabiner/assets/complex_modifications 增加自己的规则。
  • Devices,选择要应用到的设备,比如内置键盘。
  • Profiles,定义多套方案,按需选择。

官方文档:https://karabiner-elements.pqrs.org/docs/

社区分享:https://ke-complex-modifications.pqrs.org/

建议查看官方文档熟悉一点小例子,然后下载别人实现类似的功能的 JSON 代码来参考。

文档最下面有个 External JSON generators,比如用这个 Karabiner Complex Modification 可以自动生成 JSON,不过简单的映射还是直接写 JSON 方便。

我的键位设置

原始的 Emacs-like 键位:

Ctrl + ...
a 移动到行首
e 移动到行尾
b 左
f 右
p 上
n 下
h 往左删除一个
d 往右删除一个
w 往左删除单词
u 往左删除到行首
k 往右删除到行尾

自定义的的键位:

Ctrl + ...
a 移动到行首
e 移动到行尾
h 左
l 右
k 上
j 下
o 插入一行
s 往左删除一个
d 往右删除一个
u 往左删除到行首
i 往右删除到行尾
w 往左删除单词

pnbf 仍然可以使用,没有冲突。

a e 不进行任何改动,我还没有发现有不支持的地方。

w u 的功能虽然和上面一样,但有些地方比如 Typora 就不支持,自己实现一遍确保全局有效。

代码

  • (1) CapsLock 短按为 Esc,组合为 Control
  • (2) Ctrl + hjkl 移动光标,排除 JetBrains
    • 上下左右修改为 hjkl,与原来的 pnbf 不冲突,仍然可以使用;
    • 排除 JetBrains,IDEA 自己单独实现普通模式和插入模式不同的行为。
  • (3) Ctrl + aeosduiw,排除 JetBrains 和终端
    • ae 不改动,wu 重新实现了一遍;
    • o 和 Vim 的功能类似,在本行的任何位置直接新建一行并切换过去;
    • 增加了 s、i 两个键位在原来的 d、u 旁边,左边的往左删,右边的往右删,很好记忆;
    • 排除 JetBrains 和终端。
  • (4) Option + [Shift] + hjkl
    • 可以用 Option [+ Shift] + Ctrl + h/l,但按键太多,改成直接按 h/l 也生效;
    • Option + j/k 用于 VSCode 默认快捷键,上下移动此行;
    • Option + Shift + j/k 用于 VSCode 默认快捷键,复制一行;

将代码保存到 ~/.config/karabiner/assets/complex_modifications

因为这个 issue#2774 没有解决,还不能全局限定或屏蔽某 App,代码像面条一样,frontmost_application_unless 相关配置每次修改时都需要同时改动多处地方。

{
  "title": "Dvel's hjkl",
  "rules": [
    {
      "description": "(1) CapsLock 短按为 Esc,组合为 Control",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "caps_lock",
            "modifiers": {
              "optional": [
                "any"
              ]
            }
          },
          "to": [
            {
              "key_code": "left_control",
              "lazy": true
            }
          ],
          "to_if_alone": [
            {
              "key_code": "escape"
            }
          ]
        }
      ]
    },
    {
      "description": "(2) Ctrl + hjkl 移动光标,排除 JetBrains",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "h",
            "modifiers": {
              "mandatory": [
                "left_control"
              ],
              "optional": [
                "any"
              ]
            }
          },
          "to": [
            {
              "key_code": "left_arrow"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "jetbrains"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "j",
            "modifiers": {
              "mandatory": [
                "left_control"
              ],
              "optional": [
                "any"
              ]
            }
          },
          "to": [
            {
              "key_code": "down_arrow"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "jetbrains"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "k",
            "modifiers": {
              "mandatory": [
                "left_control"
              ],
              "optional": [
                "any"
              ]
            }
          },
          "to": [
            {
              "key_code": "up_arrow"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "jetbrains"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "l",
            "modifiers": {
              "mandatory": [
                "left_control"
              ],
              "optional": [
                "any"
              ]
            }
          },
          "to": [
            {
              "key_code": "right_arrow"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "jetbrains"
              ]
            }
          ]
        }
      ]
    },
    {
      "description": "(3) Ctrl + aeosduiw,排除 JetBrains 和终端",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "o",
            "modifiers": {
              "mandatory": [
                "left_control"
              ]
            }
          },
          "to": [
            {
              "key_code": "right_arrow",
              "modifiers": [
                "left_command"
              ]
            },
            {
              "key_code": "return_or_enter"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "jetbrains",
                "Terminal",
                "iterm",
                "Warp"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "s",
            "modifiers": {
              "mandatory": [
                "left_control"
              ]
            }
          },
          "to": [
            {
              "key_code": "delete_or_backspace"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "jetbrains",
                "Terminal",
                "iterm",
                "Warp"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "d",
            "modifiers": {
              "mandatory": [
                "left_control"
              ]
            }
          },
          "to": [
            {
              "key_code": "delete_forward"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "jetbrains",
                "Terminal",
                "iterm",
                "Warp"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "u",
            "modifiers": {
              "mandatory": [
                "left_control"
              ]
            }
          },
          "to": [
            {
              "key_code": "left_arrow",
              "modifiers": [
                "left_command",
                "left_shift"
              ]
            },
            {
              "key_code": "delete_or_backspace"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "jetbrains",
                "Terminal",
                "iterm",
                "Warp"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "i",
            "modifiers": {
              "mandatory": [
                "left_control"
              ]
            }
          },
          "to": [
            {
              "key_code": "right_arrow",
              "modifiers": [
                "left_command",
                "left_shift"
              ]
            },
            {
              "key_code": "delete_forward"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "jetbrains",
                "Terminal",
                "iterm",
                "Warp"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "w",
            "modifiers": {
              "mandatory": [
                "left_control"
              ]
            }
          },
          "to": [
            {
              "key_code": "left_arrow",
              "modifiers": [
                "option",
                "shift"
              ]
            },
            {
              "key_code": "delete_or_backspace"
            }
          ],
          "conditions": [
            {
              "type": "frontmost_application_unless",
              "bundle_identifiers": [
                "jetbrains",
                "Terminal",
                "iterm",
                "Warp"
              ]
            }
          ]
        }
      ]
    },
    {
      "description": "(4) Option [+ Shift] + hjkl",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "h",
            "modifiers": {
              "mandatory": [
                "option"
              ],
              "optional": [
                "shift"
              ]
            }
          },
          "to": [
            {
              "key_code": "left_arrow",
              "modifiers": [
                "option"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "j",
            "modifiers": {
              "mandatory": [
                "option"
              ],
              "optional": [
                "shift"
              ]
            }
          },
          "to": [
            {
              "key_code": "down_arrow",
              "modifiers": [
                "option"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "k",
            "modifiers": {
              "mandatory": [
                "option"
              ],
              "optional": [
                "shift"
              ]
            }
          },
          "to": [
            {
              "key_code": "up_arrow",
              "modifiers": [
                "option"
              ]
            }
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "l",
            "modifiers": {
              "mandatory": [
                "option"
              ],
              "optional": [
                "shift"
              ]
            }
          },
          "to": [
            {
              "key_code": "right_arrow",
              "modifiers": [
                "option"
              ]
            }
          ]
        }
      ]
    }
  ]
}
☑️ ☆

macOS 修改 Emacs 键位为 Vim 键位

macOS 默认在文本编辑中使用 pnbf 来进行上下左右,还有一些其他键位,但我习惯使用 Vim 的 hjkl 来移动。

我已经在 Vim 中设置好了,但希望这个键位应用到全局。


通过修改 DefaultKeyBinding.dict 来完成。

但只能适配一般输入框的快捷键,很多编辑器都还是老样子,无法覆盖。

比如 Firefox 的输入框、Alfred 的输入框是可以的,但是很多网页编辑框、终端、代码编辑器、Typora 等等还是老键位。


因为这个方法的不一致性,我已经改用 Karabiner 了。

通过 Karabiner 修改,则会真·强制·全局。

不过还是记录一下这种方法。

下一篇博文:macOS 利用 Karabiner 修改 Emacs 键位为 Vim 键位

macOS 默认的 Emacs-like 键位

官方支持文档:Mac 键盘快捷键

Control-A:移至行或段落的开头。

Control-E:移至行或段落的末尾。

Control-F:向前移动一个字符。

Control-B:向后移动一个字符。

Control-L:将光标或所选内容置于可见区域中央。

Control-P:上移一行。

Control-N:下移一行。

Control-O:在插入点后新插入一行。

Control-T:将插入点后面的字符与插入点前面的字符交换。

Control-H:删除插入点左边的字符。也可以使用 Delete 键。

Control-D:删除插入点右边的字符。也可以使用 Fn-Delete。

Control-K:删除插入点与行或段落末尾处之间的文本。

键位映射文件

默认的键位设置在:

/System/Library/Frameworks/AppKit.framework/Resources/StandardKeyBinding.dict

可以自己增加一个来覆盖默认的:(默认没有 KeyBindings 目录,可以自己创建)

~/Library/KeyBindings/DefaultKeyBinding.dict

查看 StandardKeyBinding.dict 的方法:

可以用 Xcode 打开,选择文件类型为 Property List Binary。

或者可以用 plutil 转换为 XML:

plutil -convert xml1 -o StandardKeyBinding.xml  /System/Library/Frameworks/AppKit.framework/Resources/StandardKeyBinding.dict

转换完成后,可以得到类似下面这样的代码:

	<key>^a</key>
	<string>moveToBeginningOfParagraph:</string>
	<key>^b</key>
	<string>moveBackward:</string>
	<key>^d</key>
	<string>deleteForward:</string>
	<key>^e</key>
	<string>moveToEndOfParagraph:</string>
	<key>^f</key>
	<string>moveForward:</string>
	<key>^h</key>
	<string>deleteBackward:</string>
	<key>^k</key>
	<string>deleteToEndOfParagraph:</string>
	<key>^l</key>
	<string>centerSelectionInVisibleArea:</string>
	<key>^n</key>
	<string>moveDown:</string>
字符 按键
"@" Command
"^" Control
"~" Option

^A 这样的代表 Control + Shift + a。

修改示例

我自己的习惯,除了 hjkl 移动还改了一点别的。

创建并编辑 ~/Library/KeyBindings/DefaultKeyBinding.dict

{
  "^a" = "moveToBeginningOfParagraph:";  // 移动到行首
  "^e" = "moveToEndOfParagraph:";        // 移动到行尾
  "^h" = "moveBackward:";                // 左
  "^l" = "moveForward:";                 // 右
  "^k" = "moveUp:";                      // 上
  "^j" = "moveDown:";                    // 下
  "^o" = "insertNewline:";               // 插入一行
  "^s" = "deleteBackward:";              // 往左删除一个
  "^d" = "deleteForward:";               // 往右删除一个
  "^w" = "deleteWordBackward:";          // 往左删除单词
  "^u" = "deleteToBeginningOfLine:";     // 往左删除到行首
  "^i" = "deleteToEndOfParagraph:";      // 往右删除到行尾
  // 有 Shift 的(产生选区)
  "^A" = "moveToBeginningOfParagraphAndModifySelection:";
  "^E" = "moveToEndOfParagraphAndModifySelection:";
  "^H" = "moveBackwardAndModifySelection:";
  "^L" = "moveForwardAndModifySelection:";
  "^K" = "moveUpAndModifySelection:";
  "^J" = "moveDownAndModifySelection:";
}

保存后需要重启 App 来查看效果。

参考:

Cocoa Event Handling Guide

NSStandardKeyBindingResponding

Customizing the Cocoa Text System

What are the control keys available when working in a text field?

☑️ ⭐

Surge 配置

原来一直在用机场订阅自带的配置,这回快到期了想换换,同时在用多家,顺便学习了一下 Surge 配置。

一些教程和指引

一些仓库

常见的配置方法

黑名单模式:除了我规定的走代理,剩下统统走直连。

白名单模式:除了我规定的走直连,剩下统统走代理。

我个人是觉得白名单模式比较好,省事儿,搭配一些需要直连的规则和 GEOIP,CN,DIRECT 很方便。

配置思路就是:

  • 把特殊的规则放在最上面。比如 JetBrains 在国内是可以访问的,Steam 下载游戏和 BT 之类的没必要走代理浪费流量。
  • 需要配置开关的,可以单独弄一个类型,比如是测速时是走代理还是直连的 SpeedTest。
  • 接下来是国内的规则集,命中就直连。
    • 还有一个 ChinaMax 规则集,比较大,我没试过。
  • 最后是判断 GeoIP,是 CN 的就直连
  • 规则全部检查完了,FINAL 走代理。

我的配置

[General]
loglevel = notify
skip-proxy = 127.0.0.1, 192.168.0.0/16, 193.168.0.0/24, 10.0.0.0/8, 172.16.0.0/12, 100.64.0.0/10, localhost, *.local
exclude-simple-hostnames = true
internet-test-url = http://taobao.com/
proxy-test-url = http://cp.cloudflare.com/generate_204
test-timeout = 2
geoip-maxmind-url = https://github.com/Hackl0us/GeoIP2-CN/raw/release/Country.mmdb
dns-server = 223.5.5.5, 223.6.6.6, 119.29.29.29, 114.114.114.114, system
encrypted-dns-server = https://dns.alidns.com/dns-query, https://doh.pub/dns-query
use-local-host-item-for-proxy = true
show-error-page-for-reject = true
ipv6 = false

[Proxy]
🇨🇳Direct = direct
⛔️Reject = reject

[Proxy Group]
Final = select, 🌎Proxy, 🇨🇳Direct
SpeedTest = select, 🇨🇳Direct, 🌎Proxy
🛡Guard = select, ⛔️Reject, 🇨🇳Direct
🌎Proxy = select, 机场1, 机场2
机场1 = url-test, policy-path=订阅地址
机场2 = url-test, policy-path=订阅地址

[Rule]
# ################## 置顶一些特殊的
DOMAIN-SUFFIX,jetbrains.com,DIRECT
DOMAIN-SUFFIX,bing.com,🌎Proxy
# ################## 下载类
PROCESS-NAME,aria2c,DIRECT
PROCESS-NAME,fdm,DIRECT
PROCESS-NAME,Folx,DIRECT
PROCESS-NAME,NetTransport,DIRECT
PROCESS-NAME,Transmission,DIRECT
PROCESS-NAME,uTorrent,DIRECT
PROCESS-NAME,WebTorrent,DIRECT
PROCESS-NAME,WebTorrent Helper,DIRECT
PROCESS-NAME,qbittorrent,DIRECT
PROCESS-NAME,Motrix,DIRECT
PROCESS-NAME,Thunder,DIRECT
PROCESS-NAME,nwjs,DIRECT // 城通网盘
PROCESS-NAME,qiyimac,DIRECT
PROCESS-NAME,QQLive,DIRECT
# ################## 规则集 https://github.com/blackmatrix7/ios_rule_script
# SystemOTA
RULE-SET,https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/rule/Surge/SystemOTA/SystemOTA.list,DIRECT
# SteamCN
RULE-SET,https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/rule/Surge/SteamCN/SteamCN.list,DIRECT
# 测速
RULE-SET,https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/rule/Surge/Speedtest/Speedtest.list,SpeedTest
# 去广告
DOMAIN-SET,https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/rule/Surge/Advertising/Advertising_Domain.list,🛡Guard
RULE-SET,https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/rule/Surge/Advertising/Advertising.list,🛡Guard
# 🇨🇳国内
DOMAIN-SET,https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/rule/Surge/China/China_Domain.list,DIRECT
RULE-SET,https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/rule/Surge/China/China.list,DIRECT
RULE-SET,https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/rule/Surge/ChinaMedia/ChinaMedia.list,DIRECT
# 🌎国际
# DOMAIN-SET,https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/rule/Surge/GlobalMedia/GlobalMedia_Domain.list,🌎Proxy
# RULE-SET,https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/rule/Surge/GlobalMedia/GlobalMedia.list,🌎Proxy
# DOMAIN-SET,https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/rule/Surge/Proxy/Proxy_Domain.list,🌎Proxy
# RULE-SET,https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/rule/Surge/Proxy/Proxy.list,🌎Proxy
# DOMAIN-SET,https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/rule/Surge/Global/Global_Domain.list,🌎Proxy
# RULE-SET,https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/rule/Surge/Global/Global.list,🌎Proxy
# 系统请求 & 局域网 & GeoIP China
RULE-SET,SYSTEM,DIRECT
RULE-SET,LAN,DIRECT
GEOIP,CN,DIRECT
# 最终
FINAL,Final,dns-failed

[Host]
amplifi.lan = server:syslib
router.synology.com = server:syslib
sila.razer.com = server:syslib
router.asus.com = server:syslib
routerlogin.net = server:syslib
orbilogin.com = server:syslib
www.LinksysSmartWiFi.com = server:syslib
LinksysSmartWiFi.com = server:syslib
myrouter.local = server:syslib
www.miwifi.com = server:syslib
miwifi.com = server:syslib
mediarouter.home = server:syslib
tplogin.cn = server:syslib
tplinklogin.net = server:syslib
melogin.cn = server:syslib
falogin.cn = server:syslib
_hotspot_.m2m = server:syslib
hotspot.cslwifi.com = server:syslib
*.lan = server:syslib

[URL Rewrite]
# wiki 的 m 移动站点及其他语言的重写
https://zh.(m.)?wikipedia.org/(wiki|zh|zh-sg|zh-tw|zh-hans)/(.*) https://zh.wikipedia.org/zh-cn/$3 302

白名单模式,除了命中的规则外,全部走代理。

Final 改成直连,解开「🌎国际」的那些规则,就是黑名单模式。

额外再用上 blackmatrix7/ios_rule_script 的一些复写和脚本去广告模块:Advertising 和 AdvertisingScript。

Firefox 额外需要手动设置,不会自动信任本地的证书。

分流规则

blackmatrix7/ios_rule_script

按目录找到需要的规则,比如 rule → Surge → Advertising

在里面的 README 里写有此规则的大概说明。

但只给出了一个订阅链接,比如这个去广告的。除了复制 Advertising.list 的链接,还需要手动找到 Advertising_Domain.list 的,然后再把 Advertising_MITM.sgmodule 这个模块装上。

订阅筛选

因为订阅的节点经常会有变动,又想用自己的配置,还是使用 policy-path 的功能比较好。

可以用正则表达式过滤或挑选,常用的一些:

过滤

xxx = url-test, policy-path=订阅地址, policy-regex-filter=^((?!(关键字一|关键字二)).)*$

只包含

iepl = url-test, policy-path=订阅地址, policy-regex-filter=IEPL
新加坡 = url-test, policy-path=订阅地址, policy-regex-filter=新加坡

包含任意

policy-regex-filter=关键字一|关键字二

同时包含

policy-regex-filter=(?=.*关键字一)(?=.*关键字二).*
🔲 ☆

汉字的混乱

在为自己的 Rime 词库 进行修订时,发现了很多以前不曾了解的汉字规范。

从修订记录里翻出了一些,算是自己的吐槽吧。。。

模棱两可

「滴里耷拉、滴里搭拉、滴哩答拉」→「嘀哩嗒啦」,「啰哩啰唆」→「啰里啰唆」,「劈哩叭啦」→「噼里叭啦」,「嘁哩喀喳」→ 「嘁里咔嚓」。。。

为什么上面这些除了「里」都是口字旁,却不用同样是口字旁的「哩」呢?

「暗淡」「黯淡」。

「呆会」「待会」,表「停留」时,《现汉》未作推荐但有一个「待dāi」的一声读音专门用于这个意思,《现规》推荐使用「呆」。

「标识zhi」「标志」,「标识zhi」一直是「标志」的异形词,但《现汉》6、7 开始有了「标识shi」,《现规》直接推荐使用「标志」。

「勾拳」「钩拳」,Hook 本就是钩子的意思,我站「钩拳」。

「抗不住、抗得住」「扛不住、扛得住」。

「嚎叫」「号叫」「嗥叫」。

「定亲」是「定」,「订婚」是「订」。

各种「词」与各种「辞」。

「贻 X」与「遗 X」。

「杆」与「竿」。

「得意洋洋/洋洋得意」「得意扬扬/扬扬得意」。

「发愤图强」「奋发图强」。

「鱼钩」「渔竿」「钓鱼竿」。

「嘁嘁嚓嚓」→「嘁嘁喳喳」,「嘁哩咔喳」→「嘁里咔嚓」,不同的地方用不同的 cha。。。

「下功夫」「费工夫」。

「悲催」「悲摧」。

「备受」「倍受」。

「比划」「比画」。

「西瓜子」「西瓜籽」。

「鄙人」「敝人」。

「闭塞」「蔽塞」。

「侧足」「厕足」。

「闺中密友」的「闺密」与「闺蜜」。

「激凸」「激突」。

「叫作」「叫做」。

「做出」「作出」,以及各种「做 X」和「作 X」。

「绝不」「决不」。

「谩骂」「漫骂」。

「避邪」「辟邪」。

「泄气」「懈气」。

「佚/轶/逸」+「名/事」。。。

「蒸气」「蒸汽 = 水蒸气」。

「属望」「瞩望」。

「启航」「起航」。

「乐呵」「乐呵呵」「乐乐和和」。

「当啷」与「郎当」。

「作何」「做何」。

「订做」「定做」。

「订制」「定制」。

「实验」「试验」。

「畜生」「牲畜」。

「无名火」「无明火」。

「去除」「祛除」「驱除」。

「碾压」「碾轧」

「阵势」「阵式」

过于细致的拆分

很多字词没有作为异形词,而是分别赋予了不同的意思,或运用在不同的语境下。

校对网还看到一个 校对疑难问题分享搜集

细致的 cha:「道路分岔」「树木分杈」「头发分叉」「河流分汊」,还有个「裤裆开衩」。😅

「群山曼延」「杂草蔓延」「洪水漫延」。

「声音/水势洪大」「规模宏大」。

「作客」「做客」。

「用作」「用做」。

「年青」「年轻」。

「莫名其妙」「莫明其妙」居然不是异形词,而分别代表不同的意思,有细微的差别。

「谐调」「协调」,「协调」包含「谐调」,但多一个「使配合得当」的意思。

「嘀嗒」「滴答」,「嘀嗒」和喇叭钟表等相关,「滴答」和雨滴相关。

「淤斑」「瘀斑」、「淤血、瘀血」,前者表血液凝滞,接近西医;后者一般用于中医。

「赢利」「盈利」「营利」。

各种 meng long,日光不明用「曚昽」,月光不明用「朦胧」,目光和其余的都是「蒙眬」。

「含意」「含义」,「含义」表字词句具体的意义;「含意」表言语行为事件含有的意思(隐含的深意或言外之意)。

「片段」「片断」,前者表完整段落,后者表零碎段落。

「纯朴」「淳朴」,意思基本一致。

「泄露」「泄漏」,一般来说是「泄露秘密」和「泄漏原油」,但《现汉》里「泄露」下面写了一个「同泄漏」,「泄漏」下面写一个「同泄露」,《现规》甚至还弄了一个「泄漏消息」的例句。

「蕴含」「蕴涵」,前者是常用的,后者多用于一些学术上的。

「查/察看」「检查/察」「考查/察」「审查/察」。

「腆着胸脯」「觍着脸」是不同的 tian。

「辨证」「辩证」。

「传授」「传受」。

「画外音」「话外音」。

「见谅」「鉴谅」。

「交代」「交待」。

「宽洪」「宽宏」。

「联合」「连合」,「连合」居然不是异形词。

「弥蒙」「迷蒙」。

「起程」「启程」,《现汉》几个版本折腾两遍后,又全都收录了。

「善于」「擅于」。

「蛊惑」「鼓惑」。

「受让」「授让」。

「萦绕」「潆绕」。

「执掌」「职掌」。

「受命」「授命」。

「农田龟jun裂」与「皮肤皲jun裂」。

「撅屁股」与「噘嘴」。

「报料」「爆料」。

「抚养」「扶养」。

「急须」「急需」「亟须」「亟需」。。。

「神志」「神智」。

「肤浅」「浮浅」。

词典的问题

「嘀里嘟噜」「滴里嘟噜」,《现规》说不宜写作「滴里嘟噜」,《现汉》则都收录了,并且表示的意思不一样。

「不惟」「不唯」,《现汉》收录的是「不惟」,但怎么想也应该是「唯」,「唯」才有「但、仅、只是」的意思。

《现汉》推荐「钩心斗角」,《现规》推荐「勾心斗角」。

「蓬门荜户」「蓬门筚户」《现汉》推荐「荜」,《现规》推荐「荜」,按字义来看,「荜」是植物,「筚」是用植物做的东西,应该用「筚」比较合适。

《现汉》推荐「比画」,《现规》推荐「比划」。

「鱼钩」「渔钩」,早期《现汉》《现规》都推荐「渔钩」,后来《现汉》推荐「鱼钩」;虽然《现规》的推荐是「鱼钩→渔钩」,但是它还推荐了「渔汛→鱼汛」,好乱。

「德行」「德性」,表骂人时,《现汉》推荐「德行」,《现规》推荐「德性」。

《现汉》推荐「蹚浑水」,《现规》推荐《趟浑水》,《现规》直接将「蹚」作为「趟」的异形词,而《现汉》对「趟(一声)」的解释是「旧同蹚」。

《现汉》将「高招儿、高着儿」「绝招儿、绝着儿」「妙招儿、妙着儿」按照是否下棋作为区分,但对「昏招儿、昏着儿」却按异形词进行处理。

《现汉》推荐了「瓷器」与「磁漆」,两个 ci 并不统一。

「繁琐」「烦琐」,现在《现汉》首选「烦琐」了,感觉「繁琐」用的更多。

《现汉》推荐「榴梿」,全国科学技术名词审定委员会推荐「榴莲」(目前使用较为广泛的)。

《现汉》对「旋涡」「漩涡」来回折腾,一会这个是异形词,一会那个是异形词。

《现汉》前几版及《现规》推荐「称钱」,《现汉》现在推荐「趁钱」。

《现汉》推荐「轧马路」,《现规》推荐「压马路」。

有很多字词,全国科学技术名词审定委员会、《现代汉语词典》、《现代汉语规范词典》都不同。

对于多音字,词典没有给出那些少数的特殊读音的全部用例,比如「棱leng」还有一个 ling 音,《现汉》和《现规》的解释中都只提到「穆棱ling」,其实还有一个「丹棱ling」。这让普通人就更难寻找到正确的读法,尤其是中文搜索本就比较艰难,百度百科也没有一丁点儿可信度。比如一大堆含有多音字「陂bei pi po」的地名,完全无从寻找,难道只能问当地人吗?

专业术语的用字选择

「桔子」是「橘子」的俗写,但「橘斑幅尺蛾」根本搜不到什么,只能用「桔斑幅尺蛾」,在很多食物中也使用了「桔」。

很多计算机的书籍、论文仍在用「标识符」,应该用「标志符」,《第一批异形词整理表》就将「标识zhi」替换为了「标志」,但《现汉》同时收录了「标识zhi」和「标识shi」。

到底是「安纽夜蛾」还是「安钮夜蛾」呢。

「桑葚」「桑椹」混用的情况极多。

历史包袱

「做戏」「逢场作戏」的「做/作」。

「褴褛」「筚路蓝缕」的「褴/蓝」与「褛/缕」。

「反照」「回光返照」的「反/返」,另外《现汉》推荐的是「反照」,《现规》推荐的是「返照」。

「屏除、屏弃、屏绝」「摒除、摒弃、摒绝」,「象/像」做了细致的区分,但「屏/摒」没有做。

「伶仃洋」「零丁洋里叹零丁」。

「丰都」「酆都鬼城」。

「二心」「忠贞不贰」。

「拾遗补阙」「补缺」。

「飞语」「流言蜚语」。

「句践」→「勾践」,但是「高句丽」仍然在用「句」。

词典和民众使用习惯都已经完成转换的词汇,比如「折衷」→「折中」,但是在一些专业领域仍保留原始的写法。例如「折中」与「折衷主义疗法」、「艾鲁岛折衷鹦鹉」,又例如「鲇鱼」与许多冷门生物名词「XX鲶」。

特殊的读音

极度坑爹的读音。

「杉shan树」的木头不叫「杉shan木」,叫「杉sha木」 (╯°Д°)╯︵ ┻━┻

「栅shan极」的字典解释是:一种栅zha状电极 (╯°Д°)╯︵ ┻━┻

「溃hui脓」的字典解释是:伤口溃kui烂 (╯°Д°)╯︵ ┻━┻

「雀qiao子」的字典解释是:雀que斑 (╯°Д°)╯︵ ┻━┻

「疟yao子」的字典解释是:疟nue疾 (╯°Д°)╯︵ ┻━┻

「桑葚ren儿」 = 「桑葚shen」 (╯°Д°)╯︵ ┻━┻

「貉he」通称「貉hao子」 (╯°Д°)╯︵ ┻━┻

「荨qian麻」「荨xun麻疹」,这个应该是误读过多的影响。

「摩挲」念 mo suo 时表示「用手抚摩」,念 ma sa 时表示「用于轻轻按着并一下一下地移动」😰

几乎只在某一固定词语或中使用的读音,多数为地名,不太了解这种读音是怎么产生的,为什么在历史发展的过程中没有被其他常用读音给替换掉。

「豸zhi」的「冠豸zhai山」。

「蚌beng埠」的「蚌bang」。

「咀ju」的「尖沙咀zui」。

「厦sha」的「厦xia门」。

「柞zuo」的「柞zha水」。

「栎li」的「栎yue阳」。

「栟bing」的「栟ben茶」。

「泌mi」的「泌bi阳」。

「桧gui」唯独在人名里念「hui」,「秦桧hui」,应该是自秦桧以后没人起带「桧」的名字了。

「絜xie」唯独在人名里念「jie」,应该是简化时的附带问题。

「殖zhi」的「骨殖shi」。

「氏shi」的「阏氏zhi」。

「峙zhi」的「繁峙shi」。

「筠yun」的「筠jun连」。

「泷long」的「泷shuang水」。

「洞dong」的「洪洞tong」。

「浚jun」的「浚xun县」。

「单dan」的「单shan县」和「单chan于」。

「浰li」的「洴浰lian」。

「牟mou」的「牟mu平、中牟mu」。

「番fan」的「番pan禺」。

「磅bang」的「磅pang礴」。

「筠yun」的「筠jun连」。

「绿lv」的「绿lu林、鸭绿lu江」。

「缩suo」的「缩su砂密」。

「荠ji」的「荸荠qi」。

「荥xing阳」「荥ying经」两个地名两个音,且这个字基本上只在这两个地名中使用。

「莞wan」的「东莞guan」。

「迫po」的「迫pai击炮」。

「铅qian」的「铅yan山」。

「阿e房pang宫」。

「育yu」的「杭育yo」。

「瀑pu」的「瀑bao河」。

「缩suo」的「缩su砂密」,这个读音简直莫名其妙!

前面的读音不知道是如何产生的,但是下面这些外来的音译也用了一个很生僻的发音,感觉莫名其妙。

「哪ne吒」。

佛教用语「南na无mo」。

「呗bei」的「梵呗fan bai」。

「犍qian陀罗」,「犍qian/jian」,又生僻又是多音字。

「戛jia/ga」发 ga 音时,只用于「戛ga纳」,那为什么不用一个本来就发 ga 音的字呢?

「秘mi/bi」发 bi 音时,只用于「秘bi鲁」,那为什么不用一个本来就发 bi 音的字呢?为什么而且 Peru 的发音类似 po 而不是 bi。

「派pai/pa」发 pa 音时,只用于「派司」,表示 pass,那为什么不用一个本来就发 pa 音的字呢?

字典与日常常用的不太一样

「别介jie」(天津话的「别这样啊」),字典正字是「别价jie」。

「干嘛má」(干啥),字典正字是「干吗má」。

「瞅你咋地di」,字典正字是「咋的di」。

音译用字的混乱

「傅立叶」「傅里叶」。

「菲利浦」「飞利浦」「菲利普」。

「笛卡儿」「笛卡尔」。

「喜玛拉雅」「喜马拉雅」。

「碧昂斯」「碧昂丝」。

「汉摩拉比、汉漠拉比、汉漠拉比、汉莫拉比」「汉谟拉比」。

「札」与「扎」的混用。

「诺亚方舟」「挪亚方舟」。

「克里Kerry」「克利Klee」。

「克利斯Klis」「克里斯Chris」「克莉丝Chris」「克丽丝Kris」「科利斯Corlys」。。。

「峇株巴辖」这种译名更扯淡,「峇厘岛」已经改译为「巴厘岛」了,这译名居然弄了两个不同的 ba。

「恺撒」与「凯撒」。

还有一大堆这种混用的音译字:

爱、艾、埃

里、力、立、利、丽

思、丝、司、斯

亚、娅

哥、歌、戈、格

尼、妮

帝、迪、蒂、狄

儿、尔

伯、勃、博

福、弗、富、辅、芙、佛、

最经典的还得是下面这条修订记录:

「玛丽雅、玛丽亚、马丽娅、马里亚、马丽亚、玛利娅、玛丽娅、玛莉亚、玛俐亚」→「马利亚、玛利亚」🤦‍♂️


古希腊文学翻译家罗念生先生针对后面是否接元音分别使用了「忒」和「特」做不同的译名,如「忒修斯」「忒弥斯」都没有用「特」,而是用一个生僻的多音字「忒te tei tui」(在这里念 tè)。

这种「规则」的优劣且不说,但「统一用字」是好的,「制定并完全遵守规则」也是好的,而目前则是没有统一,也没有规则。

有朋友提了个 issue 说《原神》角色「优菈」打不出来,不明白为什么米哈游要使用一个没人用的「菈」而不使用常见的「拉」,谷歌图片搜索「菈」,出来的全是原神的内容。要是都不遵守惯用套路,译名选字还要选《通用规范汉字表》和《现代汉语词典》都没有收录的字,那真是乱套了。


最让人无语的状况莫过于一个多音字在译名中以多种发音出现,例如:

  • 伽 jia ga qie

    • 伽利略 jia li lve
    • 奥米伽 ao mi ga
    • 伽蓝 qie lan
  • 佛 fo fu

    • 佛罗里达 fo luo li da
    • 雪佛兰 xue fu lan

另外「伽」在外语原文发音为 ga,但在译名中念 jia 的情况有很多。

(╯’ - ‘)╯︵ ┻━┻

(╯’ - ‘)╯︵ ┻━┻

(╯°Д°)╯︵ ┻━┻

简繁或地区的一些不同

表示打招呼或惊诧时的语气词,繁体有个「誒」,但《现汉》《现规》《通用汉字规范表》都没有收录「诶」,大陆用的是「欸」。

「蕃茄」「番茄」,其他使用汉语的地区基本都是「蕃」,中国大陆是「番」。

简化字没有简化「噹 → 𪠽」,这个拟声词还是很常用的,字典中是以「当」代替的。

简化字没有简化「襬 → 䙓」,而是使用了「摆」,「衣摆、裙摆」。

热门但错误的商用

「比萨」「匹萨」「披萨」,「比萨」是《现汉》推荐的,但 pizza 发音明明是 pi,很多商家都在用后两个,不过比萨行业龙头之一的必胜客使用了「比萨」,现在用「比萨」的也比较多了。但日常发音与广告中都在使用 pi 的音,《现汉》却至今没有更改推荐字或读音。

「拉钩」是正确用字,但「拉勾网」用的是「勾」。

表汽车喇叭时用「嘀嘀」,但「滴滴打车」是「滴」。

告五人的歌曲《披星戴月的想你》应该用「地」。

希腊字母 sigma 的正确写法是「西格马」,但行业术语「六西格玛」使用了不同的 ma。

🔲 ☆

Netflix 中英双语字幕的好办法

使用插件 Language Reactor,目前只支持 Chrome,Firefox 很久了一直处于 coming soon 状态。

相比较其他双语插件,它本身就是个学外语的好插件,有方便的快捷键可以穿梭字幕。

但是双语字幕时有一些问题,就是英文字幕为主会带有很多拟声词之类的,太影响体验了。

而且是英文在上面,中文在下面,和平常使用的字幕反了。

2022-06-17-000977




  • 将主字幕设置为中文
  • 在 Language Reactor 设置界面的「翻译语言」设置为英文
  • 「显示音译」设置为「无音译」

2022-06-17-000979

这样体验就好多啦:

2022-06-17-000980

☑️ ☆

优化 Rime 英文输入体验

在 Rime 拼音方案中输入英文

easy_en 方案挂载到自己的主输入方案(全拼、双拼、五笔等),就可以同时输入英文单词。

我用的是 melt_eng 方案,差不多,就是拼写规则的大小写方面改了一点。

引入示例:

schema:
  dependencies:
    - melt_eng

engine:
  translators:
    - table_translator@melt_eng

melt_eng:
  dictionary: melt_eng

这样挂载了英文方案与英文词库,在拼音方案下就可以输入英文单词。

因为不是同一个翻译器,英文不会和拼音共同造词,比如输入「applediannao」,并不会自动生成「Apple电脑」,只能给常用的中英混合词汇单独弄一个词典。

期望的行为:

  • 拼音优先

输入「ma」,前几个候选项应为「吗 嘛 马」,而不是「Mac mad man」。

  • 重码时拼音优先

如「四则」与 size 冲突时,希望「四则」排在第一位

  • 短单词优先

输入 mac,第一候选项应为 Mac,而不是「马车」。

可以降低个别短单词在候选项的位置,在下文

  • 未输入完全时的英文长单词:

输入 googl,第一候选项应为 Google,而不是「个哦讴歌来」这种无意义的内容。

目前的问题:

目前的问题是这样的,输入完整的短单词权重低,输入不完整的长单词时第一候选项总是一个莫名其妙的中文。

Rime 默认的问题

权重设定

经过多次尝试,最后是给拼音和英文都设置一个权重来搞定的:

# 拼音
translator:
  dictionary: rime_ice
  initial_quality: x

# 英文
melt_eng:
  dictionary: melt_eng
  initial_quality: y

尝试过「不设置:-1」「不设置:0」「不设置:1」「1:0」「2:1」「3:2」「2:1.x」。。。

最后发现一个神奇的比例是 2 > x > y > 1。

使用了 1.2:1.1,各方面都比较合适,有点玄学😂。

期望的行为

重码问题

比如 can you she he size man,既是完整拼音,也是完整单词,在输入过该单词后,会有这样的问题:

重码问题

如果不希望这样,可以直接在词库删掉重码的单词,或永远不输入这个单词,或手动 Fn+Shift+Del 降低权重。

但推荐直接在配置文件中禁用用户词典,因为英文方案不需要造词,自然也就不需要记录到用户词典;没有用户词典,就没有调频。

禁用用户词典还有一个小优点,例如写了这样的固定顺序:

macOS	macOS	999
macOS Sierra	macOS	1
macOS High Sierra	macOS	2
macOS Mojave	macOS	3
macOS Catalina	macOS	4
macOS Big Sur	macOS	5
macOS Monterey	macOS	6
macOS Ventura	macOS	7
macOS Sonoma	macOS	8

那么输入 macos 后,候选项将始终是 macOS 排在第一,接着是最新的系统 macOS SonomamacOS Ventura ……

完整的参考配置

参考配置仓库:iDvel/rime-ice

# rime_ice.schema.yaml

# 自定义短语
custom_phrase:
  dictionary: ""
  user_dict: custom_phrase
  db_class: stabledb
  enable_completion: false
  enable_sentence: false
  initial_quality: 99  # 自定义短语给予高权重

# 拼音翻译器
translator:
  dictionary: rime_ice
  initial_quality: 1.2  # 拼音权重 1.2

# 英文翻译器
melt_eng:
  dictionary: melt_eng
  enable_sentence: false   # 禁止造句
  enable_user_dict: false  # 禁用用户词典
  initial_quality: 1.1     # 英文权重 1.1
  comment_format:          # 自定义提示码
    - xform/.*//           # 清空提示码(就是没有那个小尾巴)

解决副作用

日期时间

拼音的 initial_quality 大于 1 后,日期时间的 Lua 脚本 yield 的候选项会跑到最后面,输入 rq/sj 或 date/time 后,候选项处于最末端,date/time 还好,rq/sj 需要翻好多页。解决方法是在 yield() 前设置一个高权重:

- yield(Candidate("date", seg.start, seg._end, os.date("%Y-%m-%d"), ""))
+ local cand = Candidate("date", seg.start, seg._end, os.date("%Y-%m-%d"), "")
+ cand.quality = 100
+ yield(cand)

v 模式

英文的 initial_quality 大于 1 后,方案中的「v 模式」的候选项也跑到最后面了,输入「va」的候选项是「van vain …」,而不是「ā á ǎ à …」,可以通过 Lua 脚本解决。

短单词置顶的问题

一般输入 Mac 时,可能确实想输入这个单词,那么它在第一个候选项就没问题。

但是输入 rug 这种冷门单词时,用户大概率是想输入「如果」。

这种词汇并不多,日常使用也碰不到几个,可以手动处理。

用 Lua 解决了这个问题。(不会 Lua,感谢大佬 @Shewer Lu 指点)

然后在方案文件 xxx.schema.yaml 中可以配置:

# 增加一个 lua_filter
engine:
  filters:
    - lua_filter@*reduce_english_filter

reduce_english_filter:
  idx: 2                   # 降低到第 idx 个位置
  words: [rug, bus, ship]  # 要降低的单词

效果:

改变顺序后的效果

自动大写

autocap_filter.lua 实现,感谢 @abcdefg233 @mirtlecn 的几个 PR。

目前雾凇拼音用的是 20k 小词库,只是方便打一些常见英文,并且纯手动调整了一些大小写。

一般情况输入小写即可得到普通单词的小写形式,和大多数人名、地名、专有名词的大写形式。

通过 autocap_filter.lua 可以再将结果进行大写转换,目前的的行为:

说明 code text
输入小写,得到词库中的原样 latex LaTeX
输入首字母大写,得到首字母大写 Hello Hello
输入前2~n个字母大写,得到全大写 HEllo HELLO
同上,输入全大写,得到全大写 HELLO HELLO

拼写派生

melt_eng.schema.yaml 中修改了大量派生规则,感谢 @mirtlecn 的 PR(#326)。

派生了一些单词的拼写,对于数字同时支持了英文、全拼及双拼的拼音,也支持了部分符号标点:

单词 支持的输入方式
i18n ishiban | ieighteenn
K8s kbas | keights
.NET dotnet
Windows 11 windowsshiyi | windowseleven
Windows 2000 windowsliangqian | windowserqian
windowstwoooo | windowserooo
D&D dandd

对于中英混输词库,所有词条的编码加上了一个特殊符号前缀,防止英文方案拼写派生时派生出全大写字母:

T恤	ⓘTxu

示例:输入 txu 得到「T恤」;输入 Txu 得到「T恤」; 输入 TXU 则只会得到 TXU。

☑️ ⭐

判断字符是否为简体字或繁体字

判断一个字符是简体字还是繁体字。

Unicode 范围

开始以为可以用 Unicode 范围来表示,了解后发现简繁之间在 Unicode 字符集中位置存在交集,并且不是连续的,没办法用这种方法搞定。

如果真要靠这种办法,需要在茫茫字表中进行超级详细和繁杂的范围指定。

看到叶典网有一个字符集范围,但没有区分简繁。

Golang 也有一个 unicode.Han,也没有区分简繁。

Unihan_Variants.txt

http://www.unicode.org/Public/UCD/latest/ucd/ 下载 Unihan.zip,打开里面的 Unihan_Variants.txt

比如「战斗」的「斗」(繁体是「鬥」)这两行:

「斗」是 U+6597,「鬥」是 U+9B25

U+6597	kTraditionalVariant	U+9B25
U+9B25	kSimplifiedVariant	U+6597
  • kTraditionalVariant 前面是简体专用,后面是繁体专用;

  • kSimplifiedVariant 前面是繁体专用,后面是简体专用。

另外有「后U+540E、後U+5F8C」这种的字,「皇后」是简繁体通用,但表示 behind 时,简体用「后面」,繁体用「後面」。

所以就有以下这些行:

U+540E	kSimplifiedVariant	U+540E
U+5F8C	kSimplifiedVariant	U+540E
U+540E	kTraditionalVariant	U+540E U+5F8C
------- 翻译一下: -------
后	kSimplifiedVariant	后
後	kSimplifiedVariant	后
后	kTraditionalVariant	后 後

这种一对多、多对一的转换,除了「后/後」,还有「干/幹」等。

还有 kSpecializedSemanticVariant 这种「井/丼」的。

还有 kSpoofingVariant 这种欺骗性变体,比如「胶 U+80F6」和「㬵 U+3B35」不是同一个字。


参照 http://www.unicode.org/reports/tr38/#SCTC 查看各种标记的说明。

用不到这么精细的,不过这是个好工具啊,以后可能会用到。

自己弄个简体字表

适合简单的场景。

比如《通用汉字规范表》,但只有 8105 个字,并没有涵盖全部的简体字,比如「肏」「屄」「〇」「屌」「囧」等等很多字不在其中。

但《通用汉字规范表》缺失了大量生僻字,比如一大堆以鸟字旁、鱼字旁做为偏旁的汉字,不知道去哪里找到比较全面的字表。

opencc

如果不是太严谨的需求,临时使用的脚本等,可以考虑这个。

opencc 做简繁转换,然后对比原字符。


示例,判断字符是否为繁体字:

把字符进行繁转简。

c1 和繁转简后一致,则 c1 是简体字;

c2 和繁转简后不同,则 c2 是繁体字。

import opencc

converter = opencc.OpenCC('t2s.json')  # 繁体→简体

c1 = '门'
print(c1 == converter.convert(c1))  # '门' == '门' True

c2 = '門'
print(c2 == converter.convert(c2))  # '門' != '门' False

但并不完全成功:

import opencc

converter = opencc.OpenCC('t2s.json')  # 繁体→简体

for _, c in enumerate(['鴞', '鶲', '鮡', '鮠', '鮣', '鮋', '鮈', '鮊', '鰺', '鰏', '鰟']):
    print(c+' -> '+converter.convert(c))
    if c == converter.convert(c):
        print(c)
# 鴞 -> 鸮
# 鶲 -> 鹟
# 鮡 -> 鮡
# 鮡
# 鮠 -> 鮠
# 鮠
# 鮣 -> 䲟
# 鮋 -> 鲉
# 鮈 -> 鮈
# 鮈
# 鮊 -> 鲌
# 鰺 -> 鲹
# 鰏 -> 鲾
# 鰟 -> 鳑

试了一些超冷门的字,如「鮡 鮠 鮈」并没有被转换为「𬶐 𬶏 𬶋」。

另外还有一些问题,如「乾」会被识别为繁体字,转换为「干」。

☑️ ☆

Cloudflare 转入域名提示「出现了问题。请重试转移此域。尚未向您收费。」

Cloudflare 转入域名时出现错误提示:

「出现了问题。」

「请重试转移此域。尚未向您收费。」

英文版的:

“Something went wrong.”

“Please retry transferring this domain. You have not been charged.”

Cloudflare 出现了问题

可能是付款问题。

提示的信息过于宽泛了没有想到是这个问题,也不知道其实 Cloudflare 会在这一步直接收取费用,应该提示「付款方式出现了问题」才对。

我 Cloudflare 一直都在用免费套餐,没想着会付款,忘记了之前有一次将它从 PayPal 的「自动付款」选项中删除了。

在 Cloudflare 「控制台 - 管理账户 - 账单」中重新添加或编辑一下,再绑定上 PayPal 就好了。

Cloudflare 域名正在转移

提示 “Account specified does not exist” 时也可能是付款信息问题,比如未添加付款方式。

🔲 ☆

图片压缩:Squoosh、TinyPNG、ImageOptim、WebP

一些方便的图片压缩方法:

  • 用 Squoosh 进行格式转换、压缩。

  • 用 TinyPNG 对 JPEG、PNG、WebP 的压缩。

  • 用 ImageOptim 进行本地的 JPEG、PNG 压缩。

  • 用 Google 的 WebP CLI 工具转换为 WebP。(速度最快,推荐)

Squoosh

可以直接在浏览器打开 https://squoosh.app/ 进行压缩,支持即时预览、格式转换和很多参数调节。

也可以用 Squoosh 的 CLI 在本地压缩或转换。

TinyPNG

支持 JPEG、PNG、WebP,压缩效果和 Squoosh 差不太多,但是速度快了很多。

可以直接在浏览器打开 https://tinypng.com/ 上传 - 压缩 - 下载。

点击导航栏的 THIRD-PARTY,下面也有一些第三方的客户端,比如这个 kyleduo/TinyPNG4Mac 就不错。

Compress PNG/JPG

https://www.websiteplanet.com/zh-hans/webtools/imagecompressor/

免费又方便的线上工具,可选三种压缩模式。

ImageOptim

https://imageoptim.com/

ImageOptim 也超棒,提供了 Windows/macOS/Linux 客户端,完全本地操作,但是不支持 WebP。

WebP

WebP 是 Google 于 2010 年 09 月 30 日推出的,同时提供了有损压缩与无损压缩(可逆压缩)的图片文件格式。WebP 的设计目标是在减少文件大小的同时,达到和 JPEG、PNG、GIF 格式相同的图片质量。压缩率很高,10M 的 PNG 图片转换成 WebP 只需要大约 1~2M,而肉眼几乎不可分辨的有损压缩能压到 500KB 左右。

主流浏览器均已支持,2020 年 9 月 Safari 14 也已支持。。。

可在 https://developers.google.com/speed/webp/ 上下载。

macOS 用户可以直接使用 Homebrew 安装,安装完成后即可使用 cwebpdwebpgif2webp 等命令:

  • cwebp:转换/压缩到 WebP
  • dwebp: 将 WebP 转回 PNG
  • gif2webp: GIF 转 WebP
$ brew install webp
...
$ cwebp [options] -q quality input.png -o output.webp
$ dwebp in_file [options] [-o out_file]
$ gif2webp [options] gif_file -o webp_file

默认的压缩率是 75%:

$ cwebp input_file.png -o output_file.webp

可以使用 -q 参数调整压缩质量:

$ cwebp -q 100 input_file.png -o output_file.webp

限定大小(bytes)的有损压缩:

$ cwebp -size 102400 input_file.png -o output_file.webp

WebP 转回 PNG:

$ dwebp in_file [options] [-o out_file]

还有一个 gif2webp 命令,由于 macOS 目前还不支持预览动态 WebP 图片,本地不能直接看到很费劲(只能看到第一帧),动图依然在用 GIF。


也可以写个小脚本,放进 Alfred 里,用起来很方便:

import sys
import os

q = 75

for file in sys.argv[1:]:
    dirname = os.path.dirname(file)
    filename = os.path.splitext(os.path.basename(file))[0]
    file = file.replace(' ', '\\ ',)
    filename = filename.replace(' ', '\\ ')
    os.system(f'cwebp -q {q} {file} -o {dirname}/{filename}.webp')
    sys.stdout.write(f'"{filename}.webp" done.\n')

2022-01-23

GUI 找到一个 isparta,但是两年多没更新了。

☑️ ☆

macOS grep + sed 批量替换多个文件的内容

偶尔的替换用 VSCode 或 JetBrains IDE 的类似 Cmd + Shift + H 的文件替换就可以了。

经常或快捷的使用,可以用 grep + sed 命令进行替换。


搜索试了几个命令,发现有空格的路径会导致一些错误。

一顿搜索,找到了一个解决办法,参考 Grep word within a file then copy the file

  • --null 告诉 grep 以 NUL 字符分割文件名
  • -0 告诉 sed 以 NUL 分割输入

最终命令示例,以 bar 替换 foo

区分大小写:

$ grep -lr --null 'foo' * | xargs -0 sed -i '' 's/foo/bar/g'

不区分大小写:

$ grep -ilr --null 'foo' * | xargs -0 sed -i '' 's/foo/bar/gI'

  • grep
    • -i, --ignore-case 查找文件时不区分大小写
    • -l, --files-with-matches 返回文件名
    • -R, -r, --recursive 递归搜索子目录
  • sed
    • -i 默认 sed 会打印到标准输出,使用 -i 将直接在文件内编辑替换
    • s 替换
    • g 全局替换标志
    • I 大小写不敏感标志

示例。

替换需求:

- ![](/img/xxx.webp)
+ ![](https://example.com/img/xxx.webp)

可能有需要转义的:

查找的字符串、替换的内容:
](/img
](https://example.com/img

转义后:
](\/img
https:\/\/example.com\/img

将转义后的内容代入就可以了,完整的:

$ grep -lr --null '](\/img' | xargs -0 sed -i '' 's/](\/img/](https:\/\/example.com\/img/g'
☑️ ☆

在 macOS 根目录创建文件夹

可能有一些无法更改源码的老项目或智障项目会使用根目录来搞事情,但现在默认已经不支持对根目录进行修改了。

macOS 10.15 Catalina

在恢复模式关闭 SIP:

  1. 重启并按住 Cmd + R 进入恢复模式
  2. 菜单栏 - 实用工具 - 终端
  3. 输入命令 csrutil disable
  4. 重启

后接着执行:

$ sudo mount -uw /

重启后就可以在根目录创建文件夹了。

macOS 11 Big Sur 和 macOS 12 Monterey

不需要关闭 SIP。

直接对根目录进行软链接会报错:

$ ln -s /Users/dvel/foo/bar /bar
ln: /bar: Read-only file system

现在根目录是只读的,但可以使用 synthetic.conf 文件来创建一个虚拟链接连接到数据盘,类似 ln -s 的软链接。


假设要在根目录创建 abc 文件夹。

编辑或创建 synthetic.conf

$ sudo vim /etc/synthetic.conf

输入内容(中间是 Tab,不是空格,切勿敲错):

abc	/Users/dvel/abc

第一个是要创建的文件夹名,第二个是要链接到的现有路径。

保存后重启即可。

示例,创建后的文件夹:

在根目录创建文件夹

Tab 写成空格的解决办法

如果写成了空格,会导致频繁重启,无法进入系统。

进入恢复模式,打开终端输入命令来挂载磁盘后删除 /etc/synthetic.conf 文件::

$ diskutil apfs unlock "Macintosh HD - Data"
$ cd /Volumes/"Macintosh HD - Data"
$ cd private/etc
$ rm synthetic.conf

重启即可。

参考

What is the proper way to create a root sym link in Catalina

🔲 ☆

博客图床小妙招

不想单弄个图床了,Pages 又不花钱。

我是用在 Cloudflare Pages 或 GitHub Pages 等服务搭建的博客。

macOS 环境。

我想实现的样子:

  • 本地 Typora 能直接看图
  • 不修改图片路径就 push
  • 提交部署后,在网站也能直接看到图

Markdown 写法

Hugo 的 static/ 目录中的内容会自动放到网站根目录。

![](/img/xxx.webp)

但是这样只能在网站上显示。

只要在本机电脑上的根目录创建相同的 /img/ 文件夹就可以了。

在 macOS 根目录创建文件夹

详细的 在 macOS 根目录创建文件夹,以下是 Big Sur 和 Monterey 的方法。

编辑 synthetic.conf

$ sudo vim /etc/synthetic.conf

输入内容(中间是 Tab,不是空格):

img	/Users/dvel/Dropbox/hugo/static/img

重启后就能看到根目录有了。

在根目录创建文件夹

大功告成了,使用 ![](/img/xxx.webp) 同时支持本地和网站看图。

Typora 贴图

设置好,直接把图片粘贴到 Typora 就能得到正确路径了。

Typora 图像设置

删除也方便,不用找到文件夹,在 Typora 里右键删除文件就可以了。

其他设备

缺点就是没有发布的博文和笔记只能在自己的电脑上看图,如果经常用手机或别的电脑看的话

只能修改成完整的路径如 ![](https://example.com/img/xxx.webp) 才可以。

有多设备观看需求的,可以用一个小命令快速快捷地进行替换。

详细:macOS 批量替换多个文件的内容

示例:

$ grep -lr --null '](\/img' | xargs -0 sed -i '' 's/](\/img/](https:\/\/example.com\/img/g'
🔲 ☆

折腾 Hugo & PaperMod 主题

无意浏览了一下 Hugo 官网的主题目录,发现了一个简约风格的 Paper 主题,非常喜欢,第二天发现了一个 PaperMod,差不多算是增强版。

之前就没遇到过心爱的主题,一直将就着,好多功能也没有弄,这两天一顿折腾,记录一下折腾经过。

使用的版本是 hugo-PaperMod-7.0

开始

更新 Hugo,新建站点,导入 PaperMod 主题。

示例配置在另一个分支里,导入 PaperMod 演示站的 config.yml

配置时可以参考着 Hugo 官方文档的 Configure Hugo

另外要看看 PaperMod Wiki,PaperMod 的示例文件里好多参数都没写,比如这些参数很好用:

params:
	# ...
    DateFormat: "2006-01-02"  # 日期格式化
    ShowFullTextinRSS: true   # RSS 输出全文

HTML lang

单语言的站点可以直接设置 languageCode 并在 baseof.html 修改为:

- <html lang="{{ .Site.Language }}" dir="{{ .Language.LanguageDirection | default "auto" }}">
+ <html lang="{{ .Site.LanguageCode }}">

PaperMod 的悬浮目录

参考一:Hugo博客目录放在侧边 | PaperMod主题 | Sulv’s Blog

参考二:JannikArndt/jannikarndt.github.io@8b99f6c

设置文章中链接以新标签的方式打开

参考:在 Hugo Goldmark Markdown 中设置以新标签打开链接

修复本地图片的累计布局偏移(CLS)问题

参考:修复 Hugo 本地图片的累计布局偏移(CLS)问题

最后更新时间

看到一篇 Use Lastmod with PaperMod,这个可以和 Git 搭配,不过我希望可以手动输入。

在 PaperMod 的 post_meta.html 中加上三行:

{{- if gt .Lastmod .Date -}}
{{- $scratch.Add "meta" (slice (printf "<span title='%s'>(updated: %s)</span>" (.Lastmod) (.Lastmod | time.Format (default "January 2, 2006" .Site.Params.DateFormat)))) }}
{{- end }}

在 Hugo 的 Front Matter 下手动加上 lastmod 属性即可:

title: 折腾 Hugo & PaperMod 主题
slug: hugo-papermod-config
author: Dvel
date: 2022-01-11T15:15:38+08:00
lastmod: 2023-02-14T00:40:08+08:00

评论功能

试用了一下 Disqus,但是界面好杂乱,就换到了 Waline。

官方教程非常详尽,就是配置麻烦点,不能像 Disqus 那样一键搞定。

部署在了 Vercel,按照 Errors Accessing From China 将 CANME 记录修改成官方推荐的地址 cname-china.vercel-dns.com。(现在那个页面访问不了了,不知道是否还推荐这个。)

适配 Waline 的黑暗模式,在 PaperMod 中以 body.dark 作为选择器:

/* # Waline 黑暗模式 */
body.dark {
    /* 下面是官网默认的黑暗模式配色 */
    /* 常规颜色 */
    --waline-white: #000;
    --waline-light-grey: #666;
    --waline-dark-grey: #999;

    /* 布局颜色 */
    --waline-color: #888;
    --waline-bgcolor: #1e1e1e;
    --waline-bgcolor-light: #272727;
    --waline-border-color: #333;
    --waline-disable-bgcolor: #444;
    --waline-disable-color: #272727;

    /* 特殊颜色 */
    --waline-bq-color: #272727;

    /* 其他颜色 */
    --waline-info-bgcolor: #272727;
    --waline-info-color: #666;
}

浏览量统计

不蒜子的:

总访客:<span id="busuanzi_value_site_uv"></span>
总浏览量:<span id="busuanzi_value_site_pv"></span>
页面访问量:<span id="busuanzi_value_page_pv"></span>

Waline 的:

老版的:
<span class="waline-visitor-count" id="{{ .RelPermalink }}">
新版的:
<span class="waline-pageview-count" data-path="{{ .RelPermalink }}" />

代码高亮

Hugo 自带的配色方案是 Chroma,但 PaperMod 用的 highlight.js,用 Chroma 时样式有点问题。

设置 disableHLJS: false 选择 highlight.js 作为高亮方案。

自定义的话,由于 PaperMod 是读取写死了的文件名:

  • assets/js/highlight.min.js
  • assets/css/hljs/an-old-hope.min.css

我是直接从 highlight.js 下载最新版的 JS 和 CSS 文件,不修改文件名,直接替换掉里面的内容。

PaperMod 的代码块背景颜色是写死了的,可以在 blank.css 中设定代码块在博客为亮/暗色主题时不同的背景色。

:root {
    --hljs-bg: #282C34;
}
.dark {
    --hljs-bg: #272b33;
}

SRI

今天发现了 SRI(完整性检查)的问题,浏览器提示「Failed to find a valid digest in the ‘integrity’ attribute for resource …」,导致语法高亮失效了。

搜索后发现是因为 Hugo 的「minify」与 Cloudflare 的「Auto Minify」发生了冲突,Cloudflare 在再次压缩后导致文件哈希值改变,校验失败。

解决办法是要么关闭 SRI,要么取消 Cloudflare 的「Auto Minify」。

Cloudflare 关闭的方法:速度 - 优化 - Auto Minify。

在 Hugo 中关闭的方法:

params:
  assets:
    disableFingerprinting: true

Markdown 渲染风格

自定义了一些渲染风格,参考了一点 Simple.css 和 GitHub。

由于习惯了在 Typora 的 H2 标题前打一个空行,标准 Markdown 都不会渲染空行,所以直接增加上边距了。

PaperMod 的表格有点空洞,用了 GitHub 那种样式的表格:

样式 体验
PaperMod 有点空洞
GitHub 感觉刚刚好

和一些杂七杂八的调整。

blank.css 中新增自定义样式:

/* # Markdown 风格 */
/* 标题、正文、行内代码 - 颜色 */
:root {
    --primary: #212121;  /* rgb(30, 30, 30); */
    --content: #333333;  /* rgb(31, 31, 31); */
    --code-bg: rgba(175, 184, 193, 0.2); /* rgb(245, 245, 245); */
}
.dark {
    --code-bg: rgba(175, 184, 193, 0.2); /* rgb(55, 56, 62); */
}
/* 链接样式 */
.post-content a {
    color: #0969da;
    box-shadow: none;
    text-decoration: none;
}
.post-content a:hover {
    text-decoration: underline;
}
/* 行内代码 - 左右间距 */
.post-content code {
    margin: unset;
}
/* 代码块 - 最大高度 */
/* .post-content pre code {
    max-height: 40rem;
} */
/* 图片居中 */
.post-content img {
    margin: auto;
}
/* 行文风格 */
body {
    font-family: -apple-system, BlinkMacSystemFont, "Avenir Next", Avenir, "Nimbus Sans L", Roboto, Noto, "Segoe UI", Arial, Helvetica, "Helvetica Neue", sans-serif;
    font-size: 1rem;
    line-height: 1.5;
    margin: 0;
}
.post-content {
    padding-top: 1rem;
}
.post-content blockquote {
    color: #808080;
}
.post-content p,
.post-content blockquote,
.post-content figure,
.post-content table {
    margin: 1.15rem 0;
}
.post-content hr {
    margin: 4rem 8rem;
}
.post-content ul,
.post-content ol,
.post-content dl,
.post-content li {
    margin: 0.5rem 0;
}
.post-content h1,
.post-content h2,
.post-content h3,
.post-content h4,
.post-content h5,
.post-content h6 {
    margin-bottom: 1.15rem;
    font-weight: 600;
}
.post-content h1 {
    font-size: 2.6rem;
    margin-top: 4rem;
    border-bottom: 1px solid #ccc;
}
.post-content h2 {
    font-size: 1.8rem;
    margin-top: 4rem;
    border-bottom: 1px solid #ccc;
}
.post-content h3 {
    font-size: 1.6rem;
    margin-top: 2rem;
}
.post-content h4 {
    font-size: 1.4rem;
    margin-top: 1.44rem;
}
.post-content h5 {
    font-size: 1.2rem;
    margin-top: 1.15rem;
}
.post-content h6 {
    font-size: 1rem;
    margin-top: 1rem;
}
/* GitHub 样式的表格 */
.post-content table tr {
    border: 1px solid #979da3 !important;
}
.post-content table tr:nth-child(2n),
.post-content thead {
    background-color: var(--code-bg);
}
.post-content table th {
    border: 1px solid #979da3 !important;
}
.post-content table td {
    border: 1px solid #979da3 !important;
}
☑️ ☆

利用 Cloudflare Workers 进行批量 301 重定向

在 Cloudflare 的「网站 - 规则 - 页面规则」中可以设置 URL 转发,免费默认可以使用 3 个规则,可以进行简单的通配符转换,如果这个能满足需求那最好了。

如下:将带 www 的网址 301 重定向到不带 www 的。

匹配 URL 转发 URL 重定向
www.dvel.me/* https://dvel.me/$1 301

但是很多时候需要更复杂的 URL 转发,比如个人博客瞎折腾,网站的后缀甚至域名来回改,无法仅仅通过通配符做到全部的 301 重定向,这时候还得用 Workers。

Update:批量重定向

今天打开 Cloudflare 一看,出了个「批量重定向 Beta」。。。

但是免费版限制了 5 个列表,每个列表 20 个,如果量小的话建议用用这个,量大的用 Workers。

创建一个 Worker

创建后选择「资源 - 快速编辑」,默认代码:

// 事件监听 fetch
addEventListener("fetch", event => {
  event.respondWith(handleRequest(event.request))
})

// 返回一个只包含"Hello world"的响应
async function handleRequest(request) {
  return new Response("Hello world")
}

点击「发送」即可测试:

Cloudflare Workers

301 一个 URL 试试

将上面代码改为:

addEventListener("fetch", event => {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  return Response.redirect("https://www.google.com", 301)
}

再点击「发送」进行测试:

Cloudflare Workers

再在「触发器 - 路由 - 添加路由」中添加一个 URL 进行测试 dvel.me/test

然后在浏览器中进行访问此网址,发现这个 URL 已经成功 301 到 Google 了。

大致的做法就是这样,批量重定向只需要将路由改为 dvel.me/*,再将需要跳转的新老 URL 在代码中处理即可。

官方示例

在上图点击右上角的「示例」,可以找到两个 Redirect 的示例

第一个示例将所有的请求重定向到一个 URL:

// Redirect all requests to one URL

const destinationURL = "https://example.com"
const statusCode = 301

async function handleRequest(request) {
  return Response.redirect(destinationURL, statusCode)
}

addEventListener("fetch", async event => {
  event.respondWith(handleRequest(event.request))
})

第二个示例是仅域名互换,保留后面的 path。即 foo.com/a?b=cbar.com/a?b=c

// Redirect requests from one domain to another

const base = "https://example.com"
const statusCode = 301

async function handleRequest(request) {
  const url = new URL(request.url)
  const { pathname, search } = url
  const destinationURL = base + pathname + search

  return Response.redirect(destinationURL, statusCode)
}

addEventListener("fetch", async event => {
  event.respondWith(handleRequest(event.request))
})

这两个完全没必要,因为用「页面规则」的「URL 转发」就可以完成了,除非你 3 个免费的规则都用完了。

手写更通用的转换

一个简单的示例:如果访问的地址和 old_url 撞上了,则返回 new_url

addEventListener("fetch", event => {
    event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
    let old_url = "https://dvel.xyz/posts/1234/"
    let new_url = "https://dvel.me/posts/ba-la-ba-la/"

    if (request.url === old_url) {
        return Response.redirect(new_url, 301)
    }
    
    return fetch(request)
}

完整版(同一个域名下跳转)

效果:只重定向当前域名下指定的路径。以后要是修改路径了就在这更新一下。

旧地址 新地址
dvel.me/posts/1234/ dvel.me/posts/ba-la-ba-la/

添加触发器的路由:dvel.me/*

addEventListener("fetch", event => {
    event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
    const arr = [
        "https://dvel.me/posts/1234/ https://dvel.me/posts/ba-la-ba-la/",
        // ...
    ]

    let old_url, new_url
    for (const u2 of arr) {
        [old_url, new_url] = u2.split(' ')
        if (request.url === old_url) {
            return Response.redirect(new_url, 301)
        }
    }

    return fetch(request)
}

Cloudflare Pages 的重定向

如果在用 Cloudflare Pages 的话,用 Pages 的重定向更简单。

在构件目录创建 _redirects 文件(也就是 Hugo 的 static 目录),参考 Redirects 文档创建规则,限制是 100 个。

完整版(域名 A 跳转域名 B)

效果:默认只转换域名,并保留后面的 path 和 search;后缀和域名都变了的,需要一个一个写进数组。

旧地址 新地址
dvel.xyz/posts/1234/ dvel.me/posts/ba-la-ba-la/

添加触发器的路由:dvel.xyz/*

addEventListener("fetch", event => {
    event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
    const arr = [
        "https://dvel.xyz/posts/1234/ https://dvel.me/posts/ba-la-ba-la/",
        // ...
    ]

    let old_url, new_url
    for (const u2 of arr) {
        [old_url, new_url] = u2.split(' ')
        if (request.url === old_url) {
            return Response.redirect(new_url, 301)
        }
    }

    const base = "https://dvel.me"
    const url = new URL(request.url)
    const { pathname, search } = url
    const destinationURL = base + pathname + search
    return Response.redirect(destinationURL, 301)
}
❌