Last active
July 28, 2025 22:56
-
-
Save bensheldon/96fde03e69d4486f5bf1f8540d6b7de0 to your computer and use it in GitHub Desktop.
Normalize the YAML output of i18n-tasks so that strings are always doublequoted, or strings with newlines use YAML Literations (e.g. `key: |` style)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
module I18nTaskYamlExt | |
UNMASKED_EMOJI = / | |
(?: | |
(?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F) # base emoji | |
(?:\u200D(?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F))* # + ZWJ parts | |
) | |
/ux | |
def dump(tree, options) | |
builder = Psych::Visitors::YAMLTree.create | |
builder << tree | |
ast = builder.tree | |
_process_node(ast) | |
strip_trailing_spaces(restore_emojis(ast.to_yaml(nil, options || {}))) | |
end | |
private | |
def _process_node(node) | |
case node | |
when Psych::Nodes::Scalar | |
node.plain = false | |
node.quoted = true | |
node.style = node.value.include?("\n") ? Psych::Nodes::Scalar::LITERAL : Psych::Nodes::Scalar::DOUBLE_QUOTED # <== THE ENTIRE PURPOSE OF THIS MONKEYPATCH ๐ซ | |
node.value = _mask_emoji(node.value) if node.style == Psych::Nodes::Scalar::LITERAL # pre-mask emoji because otherwise libyaml will double-quote when we want literal style | |
when Psych::Nodes::Mapping | |
# only process the values, not the keys | |
node.children.each_slice(2) { |_key, value| _process_node(value) } | |
when Psych::Nodes::Stream, Psych::Nodes::Document, Psych::Nodes::Sequence | |
node.children.each { |node| _process_node(node) } | |
else | |
raise "not handling #{node.inspect}" | |
end | |
end | |
# libyaml will do this, but we want to do it first so that libyaml doesn't _also_ | |
# mark the node as unprintable and thus prevent it from being in a literal | |
# https://github.com/yaml/libyaml/issues/279 | |
# "Hello ๐ world ๐!" => "Hello \\u0001F44B world \\u0001F30D!" | |
def _mask_emoji(string) | |
string.gsub(UNMASKED_EMOJI) do |emoji| | |
emoji.codepoints | |
.map { |cp| format('\\u%08X', cp) } | |
.join('_') | |
end | |
end | |
end | |
I18n::Tasks::Data::Adapter::YamlAdapter.singleton_class.prepend I18nTaskYamlExt |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment