Skip to content

Instantly share code, notes, and snippets.

@bensheldon
Last active July 28, 2025 22:56
Show Gist options
  • Save bensheldon/96fde03e69d4486f5bf1f8540d6b7de0 to your computer and use it in GitHub Desktop.
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)
# 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