app/liquid_tags i18n (#16125)

This commit is contained in:
yheuhtozr 2022-01-19 01:28:38 +09:00 committed by GitHub
parent 5ec47d99dc
commit 617d66c4e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 599 additions and 364 deletions

View file

@ -25,7 +25,7 @@ class AsciinemaTag < LiquidTagBase
end
def validate(id)
raise "Invalid Asciinema ID: #{id}" unless id.match?(/\A\d+\z/)
raise I18n.t("liquid_tags.asciinema_tag.invalid_asciinema_id", id: id) unless id.match?(/\A\d+\z/)
id
end

View file

@ -23,7 +23,7 @@ class BlogcastTag < LiquidTagBase
def parse_id_or_url(input)
match = pattern_match_for(input, REGEXP_OPTIONS)
raise StandardError, "Invalid Blogcast ID" unless match
raise StandardError, I18n.t("liquid_tags.blogcast_tag.invalid_blogcast_id") unless match
match[:video_id]
end

View file

@ -32,7 +32,9 @@ class CodepenTag < LiquidTagBase
# Validation
validated_options = options.filter_map { |option| valid_option(option) }
raise StandardError, "Invalid Options" unless options.empty? || !validated_options.empty?
unless options.empty? || !validated_options.empty?
raise StandardError, I18n.t("liquid_tags.codepen_tag.invalid_options")
end
option = validated_options.join("&")
@ -52,7 +54,7 @@ class CodepenTag < LiquidTagBase
end
def raise_error
raise StandardError, "Invalid CodePen URL"
raise StandardError, I18n.t("liquid_tags.codepen_tag.invalid_codepen_url")
end
end

View file

@ -31,7 +31,7 @@ class CodesandboxTag < LiquidTagBase
return [match[:id], parse_options(match[:options]&.split("&"))] if match
id = input.split.first
raise StandardError, "CodeSandbox Error: Invalid ID" unless valid_id?(id)
raise StandardError, I18n.t("liquid_tags.codesandbox_tag.invalid_id") unless valid_id?(id)
[id, parse_options(extract_options(input))]
end

View file

@ -26,7 +26,7 @@ class CommentTag < LiquidTagBase
def parse_id_code(input)
match = pattern_match_for(input, REGEXP_OPTIONS)
raise StandardError, "Invalid Comment ID or URL" unless match
raise StandardError, I18n.t("liquid_tags.comment_tag.invalid_comment") unless match
match[:comment_id]
end

View file

@ -23,7 +23,7 @@ class DotnetFiddleTag < LiquidTagBase
def parse_link(link)
match = pattern_match_for(link, [REGISTRY_REGEXP])
raise StandardError, "Invalid DotnetFiddle URL" unless match
raise StandardError, I18n.t("liquid_tags.dotnet_fiddle_tag.invalid_dotnetfiddle_url") unless match
insert_widget(link, match)
end

View file

@ -7,7 +7,7 @@ class GistTag < LiquidTagBase
def initialize(_tag_name, link, _parse_context)
super
raise StandardError, "Invalid Gist link: You must provide a Gist link" if link.blank?
raise StandardError, I18n.t("liquid_tags.gist_tag.invalid_gist_link") if link.blank?
@uri = build_uri(link)
end
@ -40,7 +40,7 @@ class GistTag < LiquidTagBase
input_no_space
else
raise StandardError,
"Invalid Gist link: #{link} Links must follow this format: https://gist.github.com/username/gist_id"
I18n.t("liquid_tags.gist_tag.invalid_format", link: link)
end
end
@ -48,7 +48,7 @@ class GistTag < LiquidTagBase
option_no_space = option.strip
return "?#{option_no_space}" if valid_option?(option_no_space)
raise StandardError, "Invalid Filename"
raise StandardError, I18n.t("liquid_tags.gist_tag.invalid_filename")
end
def valid_link?(link)

View file

@ -31,7 +31,7 @@ class GitPitchTag < LiquidTagBase
end
def raise_error
raise StandardError, "Invalid GitPitch URL"
raise StandardError, I18n.t("liquid_tags.git_pitch_tag.invalid_gitpitch_url")
end
end

View file

@ -26,7 +26,7 @@ class GithubTag
locals: {
body: @body,
created_at: @created_at.rfc3339,
date: @created_at.utc.strftime("%b %d, %Y"),
date: I18n.l(@created_at.utc, format: :github),
html_url: @content_json[:html_url],
issue_number: issue_number,
tagline: tagline,
@ -53,7 +53,7 @@ class GithubTag
end
def generate_api_link(input)
uri = URI.parse(input).normalize
uri = Addressable::URI.parse(input).normalize
uri.host = nil if uri.host == "github.com"
# public PRs URLs are "/pull/{id}" but the API requires "/pulls/{id}"
@ -65,7 +65,7 @@ class GithubTag
if uri.fragment&.start_with?("issuecomment-")
uri.path = uri.path.gsub(%r{(issues|pulls)/\d+}, "issues/comments/")
comment_id = uri.fragment.split("-").last
uri.merge!(comment_id)
uri.join!(comment_id)
end
# fragments and query params are not needed in the API call
@ -75,7 +75,7 @@ class GithubTag
# remove leading forward slash in the path
path = uri.path.delete_prefix("/")
URI.parse(API_BASE_ENDPOINT).merge(path).to_s
Addressable::URI.parse(API_BASE_ENDPOINT).join(path).to_s
end
def valid_link?(link)
@ -89,11 +89,11 @@ class GithubTag
end
def raise_error
raise StandardError, "Invalid GitHub issue, pull request or comment link"
raise StandardError, I18n.t("liquid_tags.github_tag.github_issue_tag.invalid_github_issue_pull")
end
def title
content_json[:title] || "Comment for"
content_json[:title] || I18n.t("liquid_tags.github_tag.github_issue_tag.comment_for")
end
def issue_number
@ -101,7 +101,7 @@ class GithubTag
end
def tagline
@is_issue ? "posted on" : "commented on"
@is_issue ? I18n.t("liquid_tags.github_tag.github_issue_tag.posted_on") : I18n.t("liquid_tags.github_tag.github_issue_tag.commented_on") # rubocop:disable Layout/LineLength
end
end
end

View file

@ -40,7 +40,7 @@ class GithubTag
validate_options!(*options)
path.delete_suffix!("/") # remove optional trailing forward slash
repository_path = URI.parse(path)
repository_path = Addressable::URI.parse(path)
repository_path.query = repository_path.fragment = nil
[repository_path.normalize.to_s, options]
@ -50,7 +50,8 @@ class GithubTag
return if options.empty?
return if options.all? { |o| VALID_OPTIONS.include?(o) }
message = "GitHub tag: invalid options: #{options - VALID_OPTIONS} - supported options: #{VALID_OPTIONS}"
message = I18n.t("liquid_tags.github_tag.github_readme_tag.invalid_options",
invalid: (options - VALID_OPTIONS), valid: VALID_OPTIONS)
raise StandardError, message
end
@ -72,7 +73,7 @@ class GithubTag
end
def raise_error
raise StandardError, "Invalid GitHub repository path or URL"
raise StandardError, I18n.t("liquid_tags.github_tag.github_readme_tag.invalid_github_repository")
end
def clean_relative_path!(readme_html, url)

View file

@ -38,7 +38,7 @@ class GlitchTag < LiquidTagBase
def parse_id(input)
id = input.split.first
id.sub!(TILDE_PREFIX_REGEXP, "")
raise StandardError, "Invalid Glitch ID" unless valid_id?(id)
raise StandardError, I18n.t("liquid_tags.glitch_tag.invalid_glitch_id") unless valid_id?(id)
id
end
@ -68,7 +68,9 @@ class GlitchTag < LiquidTagBase
# Validation
validated_options = options.filter_map { |option| valid_option(option) }
raise StandardError, "Invalid Options" unless options.empty? || !validated_options.empty?
unless options.empty? || !validated_options.empty?
raise StandardError, I18n.t("liquid_tags.glitch_tag.invalid_options")
end
build_options(options)
end

View file

@ -23,7 +23,7 @@ class InstagramTag < LiquidTagBase
def parse_id_or_url(input)
match = pattern_match_for(input, REGEXP_OPTIONS)
raise StandardError, "Invalid Instagram ID" unless match
raise StandardError, I18n.t("liquid_tags.instagram_tag.invalid_instagram_id") unless match
match[:video_id]
end

View file

@ -32,7 +32,9 @@ class JsFiddleTag < LiquidTagBase
# Validation
validated_options = options.filter_map { |option| valid_option(option) }
raise StandardError, "Invalid Options" unless options.empty? || !validated_options.empty?
unless options.empty? || !validated_options.empty?
raise StandardError, I18n.t("liquid_tags.js_fiddle_tag.invalid_options")
end
validated_options.length.zero? ? "" : validated_options.join(",").concat("/")
end
@ -40,7 +42,7 @@ class JsFiddleTag < LiquidTagBase
def parse_link(link)
stripped_link = ActionController::Base.helpers.strip_tags(link)
the_link = stripped_link.split.first
raise StandardError, "Invalid JSFiddle URL" unless valid_link?(the_link)
raise StandardError, I18n.t("liquid_tags.js_fiddle_tag.invalid_jsfiddle_url") unless valid_link?(the_link)
the_link
end

View file

@ -33,7 +33,7 @@ class JsitorTag < LiquidTagBase
end
def jsitor_error
raise StandardError, "Invalid JSitor link. Please read the editor guide for more information"
raise StandardError, I18n.t("liquid_tags.jsitor_tag.invalid_jsitor_link")
end
end

View file

@ -49,7 +49,7 @@ class KotlinTag < LiquidTagBase
end
def raise_error
raise StandardError, "Invalid Kotlin Playground URL"
raise StandardError, I18n.t("liquid_tags.kotlin_tag.invalid_kotlin_playground")
end
end

View file

@ -28,13 +28,13 @@ class LinkTag < LiquidTagBase
# If domain is present in url check if it belongs to the app
unless domain.blank? || domain&.casecmp?(Settings::General.app_domain)
raise StandardError, "The article you're looking for does not exist: {% link #{slug} %}"
raise StandardError, I18n.t("liquid_tags.link_tag.not_exist_link", slug: slug)
end
path.slice!(0) if path.starts_with?("/") # remove leading slash if present
path.slice!(-1) if path.ends_with?("/") # remove trailing slash if present
extracted_hash = Addressable::Template.new("{username}/{slug}").extract(path)&.symbolize_keys
raise StandardError, "The article you're looking for does not exist: #{slug}" unless extracted_hash
raise StandardError, I18n.t("liquid_tags.link_tag.not_exist", slug: slug) unless extracted_hash
extracted_hash
end

View file

@ -63,13 +63,13 @@ class LiquidTagBase < Liquid::Tag
return unless self.class.const_defined? :VALID_CONTEXTS
source = parse_context.partial_options[:source]
raise LiquidTags::Errors::InvalidParseContext, "No source found" unless source
raise LiquidTags::Errors::InvalidParseContext, I18n.t("liquid_tags.liquid_tag_base.no_source_found") unless source
is_valid_source = self.class::VALID_CONTEXTS.include? source.class.name
return if is_valid_source
valid_contexts = self.class::VALID_CONTEXTS.map(&:pluralize).join(", ")
invalid_source_error_msg = "Invalid context. This liquid tag can only be used in #{valid_contexts}."
invalid_source_error_msg = I18n.t("liquid_tags.liquid_tag_base.invalid_context", valid: valid_contexts)
raise LiquidTags::Errors::InvalidParseContext, invalid_source_error_msg
end
end

View file

@ -23,10 +23,10 @@ class ListingTag < LiquidTagBase
def get_listing(url)
hash = get_hash(url)
raise StandardError, "Invalid URL or slug. Listing not found." if hash.nil?
raise StandardError, I18n.t("liquid_tags.listing_tag.invalid_url_or_slug") if hash.nil?
listing = Listing.in_category(hash[:category]).find_by(slug: hash[:slug])
raise StandardError, "Invalid URL or slug. Listing not found." unless listing
raise StandardError, I18n.t("liquid_tags.listing_tag.invalid_url_or_slug") unless listing
listing
end

View file

@ -31,7 +31,7 @@ class MediumTag < LiquidTagBase
end
def raise_error
raise StandardError, "Invalid link URL or link URL does not exist"
raise StandardError, I18n.t("liquid_tags.medium_tag.invalid_link_url")
end
end

View file

@ -21,7 +21,9 @@ class NextTechTag < LiquidTagBase
# Returns the share token from the end of the share URL.
def parse_share_url(share_url)
clean_share_url = ActionController::Base.helpers.strip_tags(share_url).delete(" ").gsub(/\?.*/, "")
raise StandardError, "Invalid Next Tech share URL" unless valid_share_url?(clean_share_url)
unless valid_share_url?(clean_share_url)
raise StandardError, I18n.t("liquid_tags.next_tech_tag.invalid_url")
end
clean_share_url.split("/").last
end

View file

@ -1,6 +1,6 @@
class NullTag < Liquid::Block
def initialize(tag_name, _markup, _options) # rubocop:disable Lint/MissingSuper
raise StandardError, "Liquid##{tag_name} tag is disabled"
raise StandardError, I18n.t("liquid_tags.null_tag.liquid_tag_is_disabled", tag_name: tag_name)
end
end

View file

@ -31,14 +31,14 @@ class OrganizationTag < LiquidTagBase
forem_domain = URL.url
if organization.starts_with?(forem_domain)
match = pattern_match_for(organization, [REGISTRY_REGEXP])
raise StandardError, "Invalid Organization URL" unless match
raise StandardError, I18n.t("liquid_tags.organization_tag.invalid_url") unless match
organization = Organization.find_by(slug: match[:org_slug])
else
organization = Organization.find_by(slug: organization)
end
raise StandardError, "Invalid organization slug" if organization.nil?
raise StandardError, I18n.t("liquid_tags.organization_tag.invalid_slug") if organization.nil?
organization
end

View file

@ -22,7 +22,7 @@ class ParlerTag < LiquidTagBase
def parse_id(input)
input_no_space = input.delete(" ")
input_no_space = input_no_space.scan(%r{\bhttps?://[a-z./0-9-]+\b}).first
raise StandardError, "Invalid Parler URL" unless valid_id?(input_no_space)
raise StandardError, I18n.t("liquid_tags.parler_tag.invalid_parler_url") unless valid_id?(input_no_space)
input_no_space
end

View file

@ -63,7 +63,7 @@ class PodcastTag < LiquidTagBase
end
def raise_error
raise StandardError, "Invalid podcast link"
raise StandardError, I18n.t("liquid_tags.podcast_tag.invalid_podcast_link")
end
end

View file

@ -123,7 +123,7 @@ class PollTag < LiquidTagBase
def find_poll(id_code)
Poll.find(id_code.to_i(26))
rescue ActiveRecord::RecordNotFound
raise StandardError, "Invalid poll ID"
raise StandardError, I18n.t("liquid_tags.poll_tag.invalid_poll_id")
end
def self.script

View file

@ -44,7 +44,7 @@ class RedditTag < LiquidTagBase
author: data["author"],
title: data["title"],
post_url: @url,
created_at: Time.zone.at(data["created_utc"]).strftime("%b %e '%y"),
created_at: I18n.l(Time.zone.at(data["created_utc"]), format: :reddit),
post_hint: data["post_hint"],
image_url: data["url"],
thumbnail: data["thumbnail"],
@ -63,12 +63,12 @@ class RedditTag < LiquidTagBase
def validate_url
return true if valid_url?(@url.delete(" ")) && (@url =~ REGISTRY_REGEXP)&.zero?
raise StandardError, "Invalid Reddit link: #{@url}"
raise StandardError, I18n.t("liquid_tags.reddit_tag.invalid_reddit_link", url: @url)
end
def valid_url?(url)
url = URI.parse(url)
url.is_a?(URI::HTTP)
url = Addressable::URI.parse(url)
%w[http https].include? url.scheme
end
end

View file

@ -22,7 +22,7 @@ class ReplitTag < LiquidTagBase
def parse_input(input)
match = pattern_match_for(input, REGEXP_OPTIONS)
raise StandardError, "Invalid Replit URL or @user/slug" unless match
raise StandardError, I18n.t("liquid_tags.replit_tag.invalid_replit_id") unless match
match[:address]
end

View file

@ -88,7 +88,7 @@ class RunkitTag < Liquid::Block
end
def sanitized_preamble(markup)
raise StandardError, "Runkit tag is invalid" if markup.include? "\">"
raise StandardError, I18n.t("liquid_tags.runkit_tag.runkit_tag_is_invalid") if markup.include? "\">"
ActionView::Base.full_sanitizer.sanitize(markup, tags: [])
end

View file

@ -19,7 +19,10 @@ class SlideshareTag < LiquidTagBase
private
def validate(key)
raise StandardError, "Invalid Slideshare Key" unless key.match?(/\A[a-zA-Z0-9]{12,14}\Z/)
unless key.match?(/\A[a-zA-Z0-9]{12,14}\Z/)
raise StandardError,
I18n.t("liquid_tags.slideshare_tag.invalid_slideshare_key")
end
key
end

View file

@ -37,7 +37,7 @@ class SoundcloudTag < LiquidTagBase
end
def raise_error
raise StandardError, "Invalid Soundcloud URL - try taking off any URL params: '?something=value'"
raise StandardError, I18n.t("liquid_tags.soundcloud_tag.invalid_soundcloud_url")
end
end

View file

@ -19,7 +19,7 @@ class SpeakerdeckTag < LiquidTagBase
def parse_id(input)
input_no_space = input.delete(" ")
raise StandardError, "Invalid Speakerdeck Id" unless valid_id?(input_no_space)
raise StandardError, I18n.t("liquid_tags.speakerdeck_tag.invalid_speakerdeck_id") unless valid_id?(input_no_space)
input_no_space
end

View file

@ -37,7 +37,7 @@ class SpotifyTag < LiquidTagBase
def parse_input(input)
match = pattern_match_for(input, REGEXP_OPTIONS)
raise StandardError, "Invalid Spotify URI or URL." unless match
raise StandardError, I18n.t("liquid_tags.spotify_tag.invalid_spotify_uri") unless match
[match[:type], match[:id]]
end

View file

@ -32,7 +32,7 @@ class StackblitzTag < LiquidTagBase
def parse_id(input)
input_no_space = input.split.first
raise StandardError, "Invalid Stackblitz Id" unless valid_id?(input_no_space)
raise StandardError, I18n.t("liquid_tags.stackblitz_tag.invalid_stackblitz_id") unless valid_id?(input_no_space)
input_no_space
end
@ -42,7 +42,8 @@ class StackblitzTag < LiquidTagBase
# Validation
validated_views = inputs.filter_map { |input_option| validator.call(input_option) }
raise StandardError, "Invalid Options" unless validated_views.length.between?(0, 1)
raise StandardError, I18n.t("liquid_tags.stackblitz_tag.invalid_options") unless validated_views.length.between?(0,
1)
validated_views.length.zero? ? "" : validated_views.join.to_s
end

View file

@ -37,7 +37,7 @@ class StackeryTag < LiquidTagBase
def validate_items(owner, repo)
return unless owner.blank? || repo.blank?
raise StandardError, "Stackery - Missing owner and/or repository name arguments"
raise StandardError, I18n.t("liquid_tags.stackery_tag.missing_argument")
end
end

View file

@ -50,7 +50,9 @@ class StackexchangeTag < LiquidTagBase
return "stackoverflow" if tag_name == "stackoverflow"
site = input.match(/[a-z.]+/i)[0]
raise StandardError, "Invalid Stack Exchange site: {% #{tag_name} #{input} %}" unless valid_site?(site)
unless valid_site?(site)
raise StandardError, I18n.t("liquid_tags.stackexchange_tag.invalid_site", tag: tag_name, input: input)
end
site
end
@ -66,11 +68,13 @@ class StackexchangeTag < LiquidTagBase
return unless response["items"].length.zero?
raise StandardError, "Couldn't find a post with that ID: {% #{tag_name} #{input} %}"
raise StandardError, I18n.t("liquid_tags.stackexchange_tag.post_not_found", tag: tag_name, input: input)
end
def get_data(input)
raise StandardError, "Invalid Stack Exchange ID: {% #{tag_name} #{input} %}" unless valid_input?(input)
unless valid_input?(input)
raise StandardError, I18n.t("liquid_tags.stackexchange_tag.invalid_id", tag: tag_name, input: input)
end
id = input.split.first

View file

@ -29,7 +29,7 @@ class TagTag < LiquidTagBase
def parse_tag_name_to_tag(input)
tag = Tag.find_by(name: input)
raise StandardError, "invalid tag name" if tag.nil?
raise StandardError, I18n.t("liquid_tags.tag_tag.invalid_tag_name") if tag.nil?
tag
end

View file

@ -51,7 +51,7 @@ class TweetTag < LiquidTagBase
def parse_id(input)
input_no_space = input.delete(" ")
raise StandardError, "Invalid Twitter Id" unless valid_id?(input_no_space)
raise StandardError, I18n.t("liquid_tags.tweet_tag.invalid_twitter_id") unless valid_id?(input_no_space)
input_no_space
end

View file

@ -44,7 +44,7 @@ class TwitchTag < LiquidTagBase
input = input.split("&")[0] # prevent param injection
match = pattern_match_for(input, REGEXP_OPTIONS)
raise StandardError, "Invalid Twitch ID, Slug or URL" unless match
raise StandardError, I18n.t("liquid_tags.twitch_tag.invalid_twitch_id") unless match
return player_url(match[:video_id]) if match.names.include?("video_id")
return clip_url(match[:clip_slug]) if match.names.include?("clip_slug")

View file

@ -41,7 +41,7 @@ class TwitterTimelineTag < LiquidTagBase
end
def raise_error
raise StandardError, "Invalid Twitter Timeline URL"
raise StandardError, I18n.t("liquid_tags.twitter_timeline_tag.invalid_url")
end
end

View file

@ -11,328 +11,331 @@ class UserSubscriptionTag < LiquidTagBase
:super_admin,
].freeze
SCRIPT = <<~JAVASCRIPT.freeze
var subscribeBtn = document.getElementById('subscribe-btn');
def self.script
<<~JAVASCRIPT.freeze
var subscribeBtn = document.getElementById('subscribe-btn');
function isUserSignedIn() {
return document.head.querySelector('meta[name="user-signed-in"][content="true"]') !== null;
}
// Hiding/showing elements
// If clearSubscribeButton is false, we will not clear out the subscription-signed-in area,
// which will allow users to re-submit their subscription if they see any error messages.
// ***************************************
function clearSubscriptionArea({ clearSubscribeButton = true } = {}) {
if (!clearSubscribeButton) {
// Allow users to try submitting again if they see an error.
hideSubscriptionSignedIn();
function isUserSignedIn() {
return document.head.querySelector('meta[name="user-signed-in"][content="true"]') !== null;
}
const subscriptionSignedOut = document.getElementById('subscription-signed-out');
if (subscriptionSignedOut) {
subscriptionSignedOut.classList.add("hidden");
// Hiding/showing elements
// If clearSubscribeButton is false, we will not clear out the subscription-signed-in area,
// which will allow users to re-submit their subscription if they see any error messages.
// ***************************************
function clearSubscriptionArea({ clearSubscribeButton = true } = {}) {
if (!clearSubscribeButton) {
// Allow users to try submitting again if they see an error.
hideSubscriptionSignedIn();
}
const subscriptionSignedOut = document.getElementById('subscription-signed-out');
if (subscriptionSignedOut) {
subscriptionSignedOut.classList.add("hidden");
}
hideResponseMessage();
const subscriberAppleAuth = document.getElementById('subscriber-apple-auth');
if (subscriberAppleAuth) {
subscriberAppleAuth.classList.add("hidden");
}
hideConfirmationModal();
}
hideResponseMessage();
const subscriberAppleAuth = document.getElementById('subscriber-apple-auth');
if (subscriberAppleAuth) {
subscriberAppleAuth.classList.add("hidden");
}
hideConfirmationModal();
}
// Hides the response message (which displays success/error messages) if it exists.
// ***************************************
function hideResponseMessage() {
const responseMessage = document.getElementById('response-message');
if (responseMessage) {
responseMessage.classList.add("hidden");
}
}
function showSignedIn() {
clearSubscriptionArea();
const subscriptionSignedIn = document.getElementById('subscription-signed-in');
if (subscriptionSignedIn) {
subscriptionSignedIn.classList.remove("hidden");
}
const profileImages = document.getElementById('profile-images');
if (profileImages) {
profileImages.classList.remove("signed-out");
profileImages.classList.add("signed-in");
}
}
function showSignedOut() {
clearSubscriptionArea();
const subscriptionSignedOut = document.getElementById('subscription-signed-out');
if (subscriptionSignedOut) {
subscriptionSignedOut.classList.remove("hidden");
}
const profileImages = document.getElementById('profile-images');
if (profileImages) {
profileImages.classList.remove("signed-in");
profileImages.classList.add("signed-out");
}
const subscriberProfileImage = document.getElementsByClassName('ltag__user-subscription-tag__subscriber-profile-image')[0];
if (subscriberProfileImage) {
subscriberProfileImage.classList.add("hidden");
}
}
function showResponseMessage(noticeType, msg) {
clearSubscriptionArea(clearSubscribeButton = false);
const responseMessage = document.getElementById('response-message');
if (responseMessage) {
responseMessage.classList.remove("hidden");
responseMessage.classList.add(`crayons-notice--${noticeType}`);
responseMessage.textContent = msg;
if (noticeType === 'danger') {
// Allow users to try resubscribing if they see an error message.
subscribeBtn.textContent = "Submit";
subscribeBtn.disabled = false;
// Hides the response message (which displays success/error messages) if it exists.
// ***************************************
function hideResponseMessage() {
const responseMessage = document.getElementById('response-message');
if (responseMessage) {
responseMessage.classList.add("hidden");
}
}
}
function showAppleAuthMessage() {
clearSubscriptionArea();
const subscriber = userData();
if (subscriber) {
function showSignedIn() {
clearSubscriptionArea();
const subscriptionSignedIn = document.getElementById('subscription-signed-in');
if (subscriptionSignedIn) {
subscriptionSignedIn.classList.remove("hidden");
}
const profileImages = document.getElementById('profile-images');
if (profileImages) {
profileImages.classList.remove("signed-out");
profileImages.classList.add("signed-in");
}
}
function showSignedOut() {
clearSubscriptionArea();
const subscriptionSignedOut = document.getElementById('subscription-signed-out');
if (subscriptionSignedOut) {
subscriptionSignedOut.classList.remove("hidden");
}
const profileImages = document.getElementById('profile-images');
if (profileImages) {
profileImages.classList.remove("signed-in");
profileImages.classList.add("signed-out");
}
const subscriberProfileImage = document.getElementsByClassName('ltag__user-subscription-tag__subscriber-profile-image')[0];
if (subscriberProfileImage) {
subscriberProfileImage.classList.add("hidden");
}
}
function showResponseMessage(noticeType, msg) {
clearSubscriptionArea(clearSubscribeButton = false);
const responseMessage = document.getElementById('response-message');
if (responseMessage) {
responseMessage.classList.remove("hidden");
responseMessage.classList.add(`crayons-notice--${noticeType}`);
responseMessage.textContent = msg;
if (noticeType === 'danger') {
// Allow users to try resubscribing if they see an error message.
subscribeBtn.textContent = "#{I18n.t('liquid_tags.user_subscription_tag.submit')}";
subscribeBtn.disabled = false;
}
}
}
function showAppleAuthMessage() {
clearSubscriptionArea();
const subscriber = userData();
if (subscriber) {
updateProfileImages('.ltag__user-subscription-tag__subscriber-profile-image', subscriber);
}
const subscriberAppleAuth = document.getElementById('subscriber-apple-auth');
if (subscriberAppleAuth) {
subscriberAppleAuth.classList.remove("hidden");
}
}
function showSubscribed() {
hideSubscriptionSignedIn();
updateSubscriberData();
const authorUsername = document.getElementById('user-subscription-tag')?.dataset.authorUsername;
const alreadySubscribedMsg = `#{I18n.t('liquid_tags.user_subscription_tag.subscribed')}`;
showResponseMessage('success', alreadySubscribedMsg);
}
function showConfirmationModal() {
const confirmationModal = document.getElementById('user-subscription-confirmation-modal');
if (confirmationModal) {
confirmationModal.classList.remove("hidden");
}
}
function hideConfirmationModal() {
const confirmationModal = document.getElementById('user-subscription-confirmation-modal');
if (confirmationModal) {
confirmationModal.classList.add("hidden");
}
}
// Updating DOM elements
// ***************************************
function updateSubscriberData() {
const subscriber = userData();
if (subscriber.email) {
updateElementsTextContent('.ltag__user-subscription-tag__subscriber-email', subscriber.email);
}
updateProfileImages('.ltag__user-subscription-tag__subscriber-profile-image', subscriber);
}
const subscriberAppleAuth = document.getElementById('subscriber-apple-auth');
if (subscriberAppleAuth) {
subscriberAppleAuth.classList.remove("hidden");
}
}
function updateElementsTextContent(identifier, value) {
const elements = document.querySelectorAll(identifier);
function showSubscribed() {
hideSubscriptionSignedIn();
updateSubscriberData();
const authorUsername = document.getElementById('user-subscription-tag')?.dataset.authorUsername;
const alreadySubscribedMsg = `You are already subscribed.`;
showResponseMessage('success', alreadySubscribedMsg);
}
function showConfirmationModal() {
const confirmationModal = document.getElementById('user-subscription-confirmation-modal');
if (confirmationModal) {
confirmationModal.classList.remove("hidden");
}
}
function hideConfirmationModal() {
const confirmationModal = document.getElementById('user-subscription-confirmation-modal');
if (confirmationModal) {
confirmationModal.classList.add("hidden");
}
}
// Updating DOM elements
// ***************************************
function updateSubscriberData() {
const subscriber = userData();
if (subscriber.email) {
updateElementsTextContent('.ltag__user-subscription-tag__subscriber-email', subscriber.email);
}
updateProfileImages('.ltag__user-subscription-tag__subscriber-profile-image', subscriber);
}
function updateElementsTextContent(identifier, value) {
const elements = document.querySelectorAll(identifier);
elements.forEach(function(element) {
element.textContent = value;
});
}
function updateProfileImages(identifier, subscriber) {
const profileImages = document.querySelectorAll(`img${identifier}`);
profileImages.forEach(function(profileImage) {
profileImage.src = subscriber.profile_image_90;
profileImage.alt = `${subscriber.username} profile image`;
});
}
function hideSubscriptionSignedIn() {
const subscriptionSignedIn = document.getElementById('subscription-signed-in');
if (subscriptionSignedIn) {
subscriptionSignedIn.classList.add("hidden");
}
}
// Adding event listeners for 'click'
// ***************************************
function addSignInClickHandler() {
const signInBtn = document.getElementById('sign-in-btn');
if (signInBtn) {
signInBtn.addEventListener('click', function(e) {
if (typeof showLoginModal !== 'undefined') {
showLoginModal();
}
});
}
}
function addConfirmationModalClickHandlers() {
if (subscribeBtn) {
subscribeBtn.addEventListener('click', function(e) {
showConfirmationModal();
elements.forEach(function(element) {
element.textContent = value;
});
}
const cancelBtn = document.getElementById('cancel-btn');
if (cancelBtn) {
cancelBtn.addEventListener('click', function(e) {
hideConfirmationModal();
function updateProfileImages(identifier, subscriber) {
const profileImages = document.querySelectorAll(`img${identifier}`);
profileImages.forEach(function(profileImage) {
profileImage.src = subscriber.profile_image_90;
profileImage.alt = `${subscriber.username} profile image`;
});
}
const closeConfirmationModal = document.getElementById('close-confirmation-modal');
if (closeConfirmationModal) {
closeConfirmationModal.addEventListener('click', function(e) {
hideConfirmationModal();
});
function hideSubscriptionSignedIn() {
const subscriptionSignedIn = document.getElementById('subscription-signed-in');
if (subscriptionSignedIn) {
subscriptionSignedIn.classList.add("hidden");
}
}
const confirmationModal = document.getElementById('confirmation-btn')
if (confirmationModal) {
confirmationModal.addEventListener('click', function(e) {
handleSubscription();
});
}
}
// API calls
// ***************************************
function submitSubscription() {
// Hide any error messages previously rendered.
hideResponseMessage();
subscribeBtn.textContent = "Submitting...";
subscribeBtn.disabled = true;
const headers = {
Accept: 'application/json',
'X-CSRF-Token': window.csrfToken,
'Content-Type': 'application/json',
}
const articleBody = document.getElementById('article-body');
const articleId = (articleBody ? articleBody.dataset.articleId : null);
const subscriber = userData();
const body = JSON.stringify(
{
user_subscription: {
source_type: 'Article',
source_id: articleId,
subscriber_email: subscriber.email
// Adding event listeners for 'click'
// ***************************************
function addSignInClickHandler() {
const signInBtn = document.getElementById('sign-in-btn');
if (signInBtn) {
signInBtn.addEventListener('click', function(e) {
if (typeof showLoginModal !== 'undefined') {
showLoginModal();
}
}
)
return fetch('/user_subscriptions', {
method: 'POST',
headers: headers,
credentials: 'same-origin',
body: body,
}).then(function(response) {
return response.json();
});
}
function fetchIsSubscribed() {
const articleBody = document.getElementById('article-body');
const articleId = (articleBody ? articleBody.dataset.articleId : null);
const params = new URLSearchParams({
source_type: 'Article',
source_id: articleId
}).toString();
const headers = {
Accept: 'application/json',
'X-CSRF-Token': window.csrfToken,
'Content-Type': 'application/json',
});
}
}
return fetch(`/user_subscriptions/subscribed?${params}`, {
method: 'GET',
headers: headers,
credentials: 'same-origin',
}).then(function(response) {
if (response.ok) {
return response.json();
} else {
console.error(`Base data error: ${response.status} - ${response.statusText}`);
function addConfirmationModalClickHandlers() {
if (subscribeBtn) {
subscribeBtn.addEventListener('click', function(e) {
showConfirmationModal();
});
}
});
}
// Handle API responses
// ***************************************
function handleSubscription() {
hideConfirmationModal(); // Close the modal once the user has confirmed.
submitSubscription().then(function(response) {
if (response.success) {
const userSubscriptionTag = document.getElementById('user-subscription-tag');
const authorUsername = (userSubscriptionTag ? userSubscriptionTag.dataset.authorUsername : null);
const successMsg = `You are now subscribed and may receive emails from ${authorUsername}`;
showResponseMessage('success', successMsg);
hideSubscriptionSignedIn();
} else {
showResponseMessage('danger', response.error);
const cancelBtn = document.getElementById('cancel-btn');
if (cancelBtn) {
cancelBtn.addEventListener('click', function(e) {
hideConfirmationModal();
});
}
});
}
function checkIfSubscribed() {
fetchIsSubscribed().then(function(response) {
const closeConfirmationModal = document.getElementById('close-confirmation-modal');
if (closeConfirmationModal) {
closeConfirmationModal.addEventListener('click', function(e) {
hideConfirmationModal();
});
}
const confirmationModal = document.getElementById('confirmation-btn')
if (confirmationModal) {
confirmationModal.addEventListener('click', function(e) {
handleSubscription();
});
}
}
// API calls
// ***************************************
function submitSubscription() {
// Hide any error messages previously rendered.
hideResponseMessage();
subscribeBtn.textContent = "#{I18n.t('liquid_tags.user_subscription_tag.submitting')}";
subscribeBtn.disabled = true;
const headers = {
Accept: 'application/json',
'X-CSRF-Token': window.csrfToken,
'Content-Type': 'application/json',
}
const articleBody = document.getElementById('article-body');
const articleId = (articleBody ? articleBody.dataset.articleId : null);
const subscriber = userData();
const isSubscriberAuthedWithApple = (subscriber.email ? subscriber.email.endsWith('@privaterelay.appleid.com') : false);
const body = JSON.stringify(
{
user_subscription: {
source_type: 'Article',
source_id: articleId,
subscriber_email: subscriber.email
}
}
)
if (response.is_subscribed) {
showSubscribed();
} else if (isSubscriberAuthedWithApple) {
showAppleAuthMessage();
} else {
updateSubscriberData();
}
});
}
// We load this JS on every Article. This is to only run it on Articles
// where the UserSubscription liquid tag is present
if (document.getElementById('user-subscription-tag')) {
// The markup defaults to signed out UX
if (isUserSignedIn()) {
showSignedIn();
addConfirmationModalClickHandlers();
// We need access to some DOM elements (i.e. csrf token, article id, userData, etc.)
document.addEventListener('DOMContentLoaded', function() {
checkIfSubscribed();
return fetch('/user_subscriptions', {
method: 'POST',
headers: headers,
credentials: 'same-origin',
body: body,
}).then(function(response) {
return response.json();
});
} else {
showSignedOut();
addSignInClickHandler();
}
}
JAVASCRIPT
function fetchIsSubscribed() {
const articleBody = document.getElementById('article-body');
const articleId = (articleBody ? articleBody.dataset.articleId : null);
const params = new URLSearchParams({
source_type: 'Article',
source_id: articleId
}).toString();
const headers = {
Accept: 'application/json',
'X-CSRF-Token': window.csrfToken,
'Content-Type': 'application/json',
}
return fetch(`/user_subscriptions/subscribed?${params}`, {
method: 'GET',
headers: headers,
credentials: 'same-origin',
}).then(function(response) {
if (response.ok) {
return response.json();
} else {
console.error(`Base data error: ${response.status} - ${response.statusText}`);
}
});
}
// Handle API responses
// ***************************************
function handleSubscription() {
hideConfirmationModal(); // Close the modal once the user has confirmed.
submitSubscription().then(function(response) {
if (response.success) {
const userSubscriptionTag = document.getElementById('user-subscription-tag');
const authorUsername = (userSubscriptionTag ? userSubscriptionTag.dataset.authorUsername : null);
// TODO: [yheuhtozr] currently no way of i18n via Ruby
const successMsg = `You are now subscribed and may receive emails from ${authorUsername}`;
showResponseMessage('success', successMsg);
hideSubscriptionSignedIn();
} else {
showResponseMessage('danger', response.error);
}
});
}
function checkIfSubscribed() {
fetchIsSubscribed().then(function(response) {
const subscriber = userData();
const isSubscriberAuthedWithApple = (subscriber.email ? subscriber.email.endsWith('@privaterelay.appleid.com') : false);
if (response.is_subscribed) {
showSubscribed();
} else if (isSubscriberAuthedWithApple) {
showAppleAuthMessage();
} else {
updateSubscriberData();
}
});
}
// We load this JS on every Article. This is to only run it on Articles
// where the UserSubscription liquid tag is present
if (document.getElementById('user-subscription-tag')) {
// The markup defaults to signed out UX
if (isUserSignedIn()) {
showSignedIn();
addConfirmationModalClickHandlers();
// We need access to some DOM elements (i.e. csrf token, article id, userData, etc.)
document.addEventListener('DOMContentLoaded', function() {
checkIfSubscribed();
});
} else {
showSignedOut();
addSignInClickHandler();
}
}
JAVASCRIPT
end
def initialize(_tag_name, cta_text, parse_context)
super
@ -351,10 +354,6 @@ class UserSubscriptionTag < LiquidTagBase
},
)
end
def self.script
SCRIPT
end
end
Liquid::Template.register_tag("user_subscription", UserSubscriptionTag)

View file

@ -28,9 +28,9 @@ class WikipediaTag < LiquidTagBase
def get_data(input)
url = ActionController::Base.helpers.strip_tags(input).strip
raise StandardError, "Invalid Wikipedia URL" unless valid_url?(url)
raise StandardError, I18n.t("liquid_tags.wikipedia_tag.invalid_wikipedia_url") unless valid_url?(url)
uri = URI.parse(url)
uri = Addressable::URI.parse(url)
lang = uri.host.split(".", 2).first
title = uri.path.split("/").last
anchor = uri.fragment
@ -72,7 +72,8 @@ class WikipediaTag < LiquidTagBase
def handle_response_error(response, input)
return if response.code == 200
raise StandardError, "Couldn't find the Wikipedia article - #{input}: #{response['detail']}"
raise StandardError,
I18n.t("liquid_tags.wikipedia_tag.article_not_found", input: input, detail: (response["detail"]))
end
def get_section_contents(response, anchor, input)
@ -86,7 +87,7 @@ class WikipediaTag < LiquidTagBase
return [text, title] if title.present?
raise StandardError, "Couldn't find the section of the Wikipedia article - #{input}"
raise StandardError, I18n.t("liquid_tags.wikipedia_tag.section_not_found", input: input)
end
def text_clean_up(text)

View file

@ -36,7 +36,7 @@ class YoutubeTag < LiquidTagBase
def parse_id_or_url(input)
match = pattern_match_for(input, REGEXP_OPTIONS)
raise StandardError, "Invalid YouTube ID or URL" unless match
raise StandardError, I18n.t("liquid_tags.youtube_tag.invalid_youtube_id") unless match
video_id = match[:video_id]
time_parameter = match[:time_parameter]

View file

@ -170,8 +170,10 @@ en:
other: is the wrong length (should be %{count} characters)
time:
formats:
json: "%b %e, %Y"
comment: "%B %d, %Y"
github: "%b %d, %Y"
json: "%b %e, %Y"
reddit: "%b %e '%y"
short: "%b %-e"
short_with_year: "%b %-e, %Y"
stackexchange: "%b %e '%y"

View file

@ -193,8 +193,10 @@ fr:
other: est la mauvaise longueur (devrait être %{count} caractères)
time:
formats:
json: "%b %e, %Y"
comment: "%B %d, %Y"
github: "%b %d, %Y"
json: "%b %e, %Y"
reddit: "%b %e '%y"
short: "%-e %b"
short_with_year: "%-e %b, %Y"
stackexchange: "%b %e '%y"

View file

@ -0,0 +1,107 @@
---
en:
liquid_tags:
asciinema_tag:
invalid_asciinema_id: 'Invalid Asciinema ID: %{id}'
blogcast_tag:
invalid_blogcast_id: Invalid Blogcast ID
codepen_tag:
invalid_codepen_url: Invalid CodePen URL
invalid_options: Invalid Options
codesandbox_tag:
invalid_id: 'CodeSandbox Error: Invalid ID'
comment_tag:
invalid_comment: Invalid Comment ID or URL
dotnet_fiddle_tag:
invalid_dotnetfiddle_url: Invalid DotnetFiddle URL
gist_tag:
invalid_filename: Invalid Filename
invalid_format: 'Invalid Gist link: %{link} Links must follow this format: https://gist.github.com/username/gist_id'
invalid_gist_link: 'Invalid Gist link: You must provide a Gist link'
git_pitch_tag:
invalid_gitpitch_url: Invalid GitPitch URL
github_tag:
github_issue_tag:
comment_for: Comment for
commented_on: commented on
invalid_github_issue_pull: Invalid GitHub issue, pull request or comment link
posted_on: posted on
github_readme_tag:
invalid_options: 'GitHub tag: invalid options: %{invalid} - supported options: %{valid}'
invalid_github_repository: Invalid GitHub repository path or URL
glitch_tag:
invalid_glitch_id: Invalid Glitch ID
invalid_options: Invalid Options
instagram_tag:
invalid_instagram_id: Invalid Instagram ID
js_fiddle_tag:
invalid_jsfiddle_url: Invalid JSFiddle URL
invalid_options: Invalid Options
jsitor_tag:
invalid_jsitor_link: Invalid JSitor link. Please read the editor guide for more information
kotlin_tag:
invalid_kotlin_playground: Invalid Kotlin Playground URL
link_tag:
not_exist: 'The article you''re looking for does not exist: %{slug}'
not_exist_link: 'The article you''re looking for does not exist: {% link %{slug} %}'
liquid_tag_base:
invalid_context: Invalid context. This liquid tag can only be used in %{valid}.
no_source_found: No source found
listing_tag:
invalid_url_or_slug: Invalid URL or slug. Listing not found.
medium_tag:
invalid_link_url: Invalid link URL or link URL does not exist
next_tech_tag:
invalid_url: Invalid Next Tech share URL
null_tag:
liquid_tag_is_disabled: Liquid#%{tag_name} tag is disabled
organization_tag:
invalid_slug: Invalid organization slug
invalid_url: Invalid Organization URL
parler_tag:
invalid_parler_url: Invalid Parler URL
podcast_tag:
invalid_podcast_link: Invalid podcast link
poll_tag:
invalid_poll_id: Invalid poll ID
reddit_tag:
invalid_reddit_link: 'Invalid Reddit link: %{url}'
replit_tag:
invalid_replit_id: Invalid Replit URL or @user/slug
runkit_tag:
runkit_tag_is_invalid: Runkit tag is invalid
slideshare_tag:
invalid_slideshare_key: Invalid Slideshare Key
soundcloud_tag:
invalid_soundcloud_url: 'Invalid Soundcloud URL - try taking off any URL params: ''?something=value'''
speakerdeck_tag:
invalid_speakerdeck_id: Invalid Speakerdeck Id
spotify_tag:
invalid_spotify_uri: Invalid Spotify URI or URL.
stackblitz_tag:
invalid_options: Invalid Options
invalid_stackblitz_id: Invalid Stackblitz Id
stackery_tag:
missing_argument: Stackery - Missing owner and/or repository name arguments
stackexchange_tag:
post_not_found: 'Couldn''t find a post with that ID: {% %{tag} %{input} %}'
invalid_id: 'Invalid Stack Exchange ID: {% %{tag} %{input} %}'
invalid_site: 'Invalid Stack Exchange site: {% %{tag} %{input} %}'
tag_tag:
invalid_tag_name: invalid tag name
tweet_tag:
invalid_twitter_id: Invalid Twitter Id
twitch_tag:
invalid_twitch_id: Invalid Twitch ID, Slug or URL
twitter_timeline_tag:
invalid_url: Invalid Twitter Timeline URL
user_subscription_tag:
submit: Submit
submitting: Submitting...
subscribed: You are already subscribed.
wikipedia_tag:
section_not_found: Couldn't find the section of the Wikipedia article - %{input}
article_not_found: 'Couldn''t find the Wikipedia article - %{input}: %{response_detail}'
invalid_wikipedia_url: Invalid Wikipedia URL
youtube_tag:
invalid_youtube_id: Invalid YouTube ID or URL

View file

@ -0,0 +1,107 @@
---
fr:
liquid_tags:
asciinema_tag:
invalid_asciinema_id: 'Invalid Asciinema ID: %{id}'
blogcast_tag:
invalid_blogcast_id: Invalid Blogcast ID
codepen_tag:
invalid_codepen_url: Invalid CodePen URL
invalid_options: Invalid Options
codesandbox_tag:
invalid_id: 'CodeSandbox Error: Invalid ID'
comment_tag:
invalid_comment: Invalid Comment ID or URL
dotnet_fiddle_tag:
invalid_dotnetfiddle_url: Invalid DotnetFiddle URL
gist_tag:
invalid_filename: Invalid Filename
invalid_format: 'Invalid Gist link: %{link} Links must follow this format: https://gist.github.com/username/gist_id'
invalid_gist_link: 'Invalid Gist link: You must provide a Gist link'
git_pitch_tag:
invalid_gitpitch_url: Invalid GitPitch URL
github_tag:
github_issue_tag:
comment_for: Comment for
commented_on: commented on
invalid_github_issue_pull: Invalid GitHub issue, pull request or comment link
posted_on: posted on
github_readme_tag:
invalid_options: 'GitHub tag: invalid options: %{invalid} - supported options: %{valid}'
invalid_github_repository: Invalid GitHub repository path or URL
glitch_tag:
invalid_glitch_id: Invalid Glitch ID
invalid_options: Invalid Options
instagram_tag:
invalid_instagram_id: Invalid Instagram ID
js_fiddle_tag:
invalid_jsfiddle_url: Invalid JSFiddle URL
invalid_options: Invalid Options
jsitor_tag:
invalid_jsitor_link: Invalid JSitor link. Please read the editor guide for more information
kotlin_tag:
invalid_kotlin_playground: Invalid Kotlin Playground URL
link_tag:
not_exist: 'The article you''re looking for does not exist: %{slug}'
not_exist_link: 'The article you''re looking for does not exist: {% link %{slug} %}'
liquid_tag_base:
invalid_context: Invalid context. This liquid tag can only be used in %{valid}.
no_source_found: No source found
listing_tag:
invalid_url_or_slug: Invalid URL or slug. Listing not found.
medium_tag:
invalid_link_url: Invalid link URL or link URL does not exist
next_tech_tag:
invalid_url: Invalid Next Tech share URL
null_tag:
liquid_tag_is_disabled: Liquid#%{tag_name} tag is disabled
organization_tag:
invalid_slug: Invalid organization slug
invalid_url: Invalid Organization URL
parler_tag:
invalid_parler_url: Invalid Parler URL
podcast_tag:
invalid_podcast_link: Invalid podcast link
poll_tag:
invalid_poll_id: Invalid poll ID
reddit_tag:
invalid_reddit_link: 'Invalid Reddit link: %{url}'
replit_tag:
invalid_replit_id: Invalid Replit URL or @user/slug
runkit_tag:
runkit_tag_is_invalid: Runkit tag is invalid
slideshare_tag:
invalid_slideshare_key: Invalid Slideshare Key
soundcloud_tag:
invalid_soundcloud_url: 'Invalid Soundcloud URL - try taking off any URL params: ''?something=value'''
speakerdeck_tag:
invalid_speakerdeck_id: Invalid Speakerdeck Id
spotify_tag:
invalid_spotify_uri: Invalid Spotify URI or URL.
stackblitz_tag:
invalid_options: Invalid Options
invalid_stackblitz_id: Invalid Stackblitz Id
stackery_tag:
missing_argument: Stackery - Missing owner and/or repository name arguments
stackexchange_tag:
post_not_found: 'Couldn''t find a post with that ID: {% %{tag} %{input} %}'
invalid_id: 'Invalid Stack Exchange ID: {% %{tag} %{input} %}'
invalid_site: 'Invalid Stack Exchange site: {% %{tag} %{input} %}'
tag_tag:
invalid_tag_name: invalid tag name
tweet_tag:
invalid_twitter_id: Invalid Twitter Id
twitch_tag:
invalid_twitch_id: Invalid Twitch ID, Slug or URL
twitter_timeline_tag:
invalid_url: Invalid Twitter Timeline URL
user_subscription_tag:
submit: Submit
submitting: Submitting...
subscribed: You are already subscribed.
wikipedia_tag:
section_not_found: Couldn't find the section of the Wikipedia article - %{input}
article_not_found: 'Couldn''t find the Wikipedia article - %{input}: %{response_detail}'
invalid_wikipedia_url: Invalid Wikipedia URL
youtube_tag:
invalid_youtube_id: Invalid YouTube ID or URL