code: func writeFingerprint(buf *bytes.Buffer, part ast.Node, braces bool) {
  	switch part := part.(type) {
  	case *ast.MsgNode:
  		for _, part := range part.Body.Children() {
  			writeFingerprint(buf, part, braces)
  		}
  	case *ast.RawTextNode:
  		buf.Write(part.Text)
  	case *ast.MsgPlaceholderNode:
  		if braces {
  			buf.WriteString("{" + part.Name + "}")
  		} else {
  			buf.WriteString(part.Name)
  		}
  	case *ast.MsgPluralNode:
  		buf.WriteString("{" + part.VarName + ",plural,")
  		for _, plCase := range part.Cases {
  			buf.WriteString("=" + strconv.Itoa(plCase.Value) + "{")
  			for _, child := range plCase.Body.Children() {
  				writeFingerprint(buf, child, true)
  			}
  			buf.WriteString("}")
  		}
  		buf.WriteString("other{")
  		for _, child := range part.Default.Children() {
  			writeFingerprint(buf, child, true)
  		}
  		buf.WriteString("}}")
  	default:
  		panic(fmt.Sprintf("unrecognized type %T", part))
  	}
  }
docs: // writeFingerprint writes the string used to fingerprint a message to the buffer.
  // if braces is true, the string written has placeholders surrounded by braces.
  // Plural messages always have braced placeholders.