From 617d66c4e5491499287738fa4337392ae2a8ddf2 Mon Sep 17 00:00:00 2001 From: yheuhtozr <84892012+yheuhtozr@users.noreply.github.com> Date: Wed, 19 Jan 2022 01:28:38 +0900 Subject: [PATCH] app/liquid_tags i18n (#16125) --- app/liquid_tags/asciinema_tag.rb | 2 +- app/liquid_tags/blogcast_tag.rb | 2 +- app/liquid_tags/codepen_tag.rb | 6 +- app/liquid_tags/codesandbox_tag.rb | 2 +- app/liquid_tags/comment_tag.rb | 2 +- app/liquid_tags/dotnet_fiddle_tag.rb | 2 +- app/liquid_tags/gist_tag.rb | 6 +- app/liquid_tags/git_pitch_tag.rb | 2 +- .../github_tag/github_issue_tag.rb | 14 +- .../github_tag/github_readme_tag.rb | 7 +- app/liquid_tags/glitch_tag.rb | 6 +- app/liquid_tags/instagram_tag.rb | 2 +- app/liquid_tags/js_fiddle_tag.rb | 6 +- app/liquid_tags/jsitor_tag.rb | 2 +- app/liquid_tags/kotlin_tag.rb | 2 +- app/liquid_tags/link_tag.rb | 4 +- app/liquid_tags/liquid_tag_base.rb | 4 +- app/liquid_tags/listing_tag.rb | 4 +- app/liquid_tags/medium_tag.rb | 2 +- app/liquid_tags/next_tech_tag.rb | 4 +- app/liquid_tags/null_tag.rb | 2 +- app/liquid_tags/organization_tag.rb | 4 +- app/liquid_tags/parler_tag.rb | 2 +- app/liquid_tags/podcast_tag.rb | 2 +- app/liquid_tags/poll_tag.rb | 2 +- app/liquid_tags/reddit_tag.rb | 8 +- app/liquid_tags/replit_tag.rb | 2 +- app/liquid_tags/runkit_tag.rb | 2 +- app/liquid_tags/slideshare_tag.rb | 5 +- app/liquid_tags/soundcloud_tag.rb | 2 +- app/liquid_tags/speakerdeck_tag.rb | 2 +- app/liquid_tags/spotify_tag.rb | 2 +- app/liquid_tags/stackblitz_tag.rb | 5 +- app/liquid_tags/stackery_tag.rb | 2 +- app/liquid_tags/stackexchange_tag.rb | 10 +- app/liquid_tags/tag_tag.rb | 2 +- app/liquid_tags/tweet_tag.rb | 2 +- app/liquid_tags/twitch_tag.rb | 2 +- app/liquid_tags/twitter_timeline_tag.rb | 2 +- app/liquid_tags/user_subscription_tag.rb | 589 +++++++++--------- app/liquid_tags/wikipedia_tag.rb | 9 +- app/liquid_tags/youtube_tag.rb | 2 +- config/locales/en.yml | 4 +- config/locales/fr.yml | 4 +- config/locales/liquid_tags/en.yml | 107 ++++ config/locales/liquid_tags/fr.yml | 107 ++++ 46 files changed, 599 insertions(+), 364 deletions(-) create mode 100644 config/locales/liquid_tags/en.yml create mode 100644 config/locales/liquid_tags/fr.yml diff --git a/app/liquid_tags/asciinema_tag.rb b/app/liquid_tags/asciinema_tag.rb index 873400b03..b8b72c82c 100644 --- a/app/liquid_tags/asciinema_tag.rb +++ b/app/liquid_tags/asciinema_tag.rb @@ -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 diff --git a/app/liquid_tags/blogcast_tag.rb b/app/liquid_tags/blogcast_tag.rb index 95fce5b4a..584b909fa 100644 --- a/app/liquid_tags/blogcast_tag.rb +++ b/app/liquid_tags/blogcast_tag.rb @@ -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 diff --git a/app/liquid_tags/codepen_tag.rb b/app/liquid_tags/codepen_tag.rb index f1ed8a718..bd5535799 100644 --- a/app/liquid_tags/codepen_tag.rb +++ b/app/liquid_tags/codepen_tag.rb @@ -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 diff --git a/app/liquid_tags/codesandbox_tag.rb b/app/liquid_tags/codesandbox_tag.rb index f2bfd541c..5229fc0c0 100644 --- a/app/liquid_tags/codesandbox_tag.rb +++ b/app/liquid_tags/codesandbox_tag.rb @@ -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 diff --git a/app/liquid_tags/comment_tag.rb b/app/liquid_tags/comment_tag.rb index aef962be3..f9d91bfa5 100644 --- a/app/liquid_tags/comment_tag.rb +++ b/app/liquid_tags/comment_tag.rb @@ -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 diff --git a/app/liquid_tags/dotnet_fiddle_tag.rb b/app/liquid_tags/dotnet_fiddle_tag.rb index d41a5d6b0..a70985e89 100644 --- a/app/liquid_tags/dotnet_fiddle_tag.rb +++ b/app/liquid_tags/dotnet_fiddle_tag.rb @@ -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 diff --git a/app/liquid_tags/gist_tag.rb b/app/liquid_tags/gist_tag.rb index 2fa89c5af..a3deda6c6 100644 --- a/app/liquid_tags/gist_tag.rb +++ b/app/liquid_tags/gist_tag.rb @@ -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) diff --git a/app/liquid_tags/git_pitch_tag.rb b/app/liquid_tags/git_pitch_tag.rb index 17fdd82d2..acc0eb27b 100644 --- a/app/liquid_tags/git_pitch_tag.rb +++ b/app/liquid_tags/git_pitch_tag.rb @@ -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 diff --git a/app/liquid_tags/github_tag/github_issue_tag.rb b/app/liquid_tags/github_tag/github_issue_tag.rb index 4d31f5a6a..4f97619f1 100644 --- a/app/liquid_tags/github_tag/github_issue_tag.rb +++ b/app/liquid_tags/github_tag/github_issue_tag.rb @@ -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 diff --git a/app/liquid_tags/github_tag/github_readme_tag.rb b/app/liquid_tags/github_tag/github_readme_tag.rb index 0e4464280..04c43cc08 100644 --- a/app/liquid_tags/github_tag/github_readme_tag.rb +++ b/app/liquid_tags/github_tag/github_readme_tag.rb @@ -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) diff --git a/app/liquid_tags/glitch_tag.rb b/app/liquid_tags/glitch_tag.rb index 31aece265..0c0e765f1 100644 --- a/app/liquid_tags/glitch_tag.rb +++ b/app/liquid_tags/glitch_tag.rb @@ -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 diff --git a/app/liquid_tags/instagram_tag.rb b/app/liquid_tags/instagram_tag.rb index acdbbe5d3..e15fe851c 100644 --- a/app/liquid_tags/instagram_tag.rb +++ b/app/liquid_tags/instagram_tag.rb @@ -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 diff --git a/app/liquid_tags/js_fiddle_tag.rb b/app/liquid_tags/js_fiddle_tag.rb index c2161b5b0..0bf98a7dd 100644 --- a/app/liquid_tags/js_fiddle_tag.rb +++ b/app/liquid_tags/js_fiddle_tag.rb @@ -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 diff --git a/app/liquid_tags/jsitor_tag.rb b/app/liquid_tags/jsitor_tag.rb index 028faeb05..5521e02fa 100644 --- a/app/liquid_tags/jsitor_tag.rb +++ b/app/liquid_tags/jsitor_tag.rb @@ -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 diff --git a/app/liquid_tags/kotlin_tag.rb b/app/liquid_tags/kotlin_tag.rb index aecbfc3c2..72b9fcb98 100644 --- a/app/liquid_tags/kotlin_tag.rb +++ b/app/liquid_tags/kotlin_tag.rb @@ -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 diff --git a/app/liquid_tags/link_tag.rb b/app/liquid_tags/link_tag.rb index e9520e891..172905750 100644 --- a/app/liquid_tags/link_tag.rb +++ b/app/liquid_tags/link_tag.rb @@ -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 diff --git a/app/liquid_tags/liquid_tag_base.rb b/app/liquid_tags/liquid_tag_base.rb index 78e36549a..f8ac4fdb3 100644 --- a/app/liquid_tags/liquid_tag_base.rb +++ b/app/liquid_tags/liquid_tag_base.rb @@ -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 diff --git a/app/liquid_tags/listing_tag.rb b/app/liquid_tags/listing_tag.rb index 96fd3272d..a97b6e89d 100644 --- a/app/liquid_tags/listing_tag.rb +++ b/app/liquid_tags/listing_tag.rb @@ -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 diff --git a/app/liquid_tags/medium_tag.rb b/app/liquid_tags/medium_tag.rb index 0dcba7bf5..a35d0440a 100644 --- a/app/liquid_tags/medium_tag.rb +++ b/app/liquid_tags/medium_tag.rb @@ -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 diff --git a/app/liquid_tags/next_tech_tag.rb b/app/liquid_tags/next_tech_tag.rb index b1ce293e8..51d5e055f 100644 --- a/app/liquid_tags/next_tech_tag.rb +++ b/app/liquid_tags/next_tech_tag.rb @@ -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 diff --git a/app/liquid_tags/null_tag.rb b/app/liquid_tags/null_tag.rb index d76082138..08f3c6f16 100644 --- a/app/liquid_tags/null_tag.rb +++ b/app/liquid_tags/null_tag.rb @@ -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 diff --git a/app/liquid_tags/organization_tag.rb b/app/liquid_tags/organization_tag.rb index c21feb8ff..ee22fa02f 100644 --- a/app/liquid_tags/organization_tag.rb +++ b/app/liquid_tags/organization_tag.rb @@ -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 diff --git a/app/liquid_tags/parler_tag.rb b/app/liquid_tags/parler_tag.rb index d94d19c53..30bfaac50 100755 --- a/app/liquid_tags/parler_tag.rb +++ b/app/liquid_tags/parler_tag.rb @@ -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 diff --git a/app/liquid_tags/podcast_tag.rb b/app/liquid_tags/podcast_tag.rb index ba1ffc452..4e55b5331 100644 --- a/app/liquid_tags/podcast_tag.rb +++ b/app/liquid_tags/podcast_tag.rb @@ -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 diff --git a/app/liquid_tags/poll_tag.rb b/app/liquid_tags/poll_tag.rb index 264ff008f..af23de090 100644 --- a/app/liquid_tags/poll_tag.rb +++ b/app/liquid_tags/poll_tag.rb @@ -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 diff --git a/app/liquid_tags/reddit_tag.rb b/app/liquid_tags/reddit_tag.rb index e5e16b9df..3277b9bf1 100644 --- a/app/liquid_tags/reddit_tag.rb +++ b/app/liquid_tags/reddit_tag.rb @@ -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 diff --git a/app/liquid_tags/replit_tag.rb b/app/liquid_tags/replit_tag.rb index e07380428..3c037e516 100644 --- a/app/liquid_tags/replit_tag.rb +++ b/app/liquid_tags/replit_tag.rb @@ -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 diff --git a/app/liquid_tags/runkit_tag.rb b/app/liquid_tags/runkit_tag.rb index 579d77880..fc3fec645 100644 --- a/app/liquid_tags/runkit_tag.rb +++ b/app/liquid_tags/runkit_tag.rb @@ -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 diff --git a/app/liquid_tags/slideshare_tag.rb b/app/liquid_tags/slideshare_tag.rb index 4e7119f64..6c36bb37d 100644 --- a/app/liquid_tags/slideshare_tag.rb +++ b/app/liquid_tags/slideshare_tag.rb @@ -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 diff --git a/app/liquid_tags/soundcloud_tag.rb b/app/liquid_tags/soundcloud_tag.rb index 1ba2b9352..eb53eba71 100644 --- a/app/liquid_tags/soundcloud_tag.rb +++ b/app/liquid_tags/soundcloud_tag.rb @@ -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 diff --git a/app/liquid_tags/speakerdeck_tag.rb b/app/liquid_tags/speakerdeck_tag.rb index 46f3a7b4e..57b83ac99 100644 --- a/app/liquid_tags/speakerdeck_tag.rb +++ b/app/liquid_tags/speakerdeck_tag.rb @@ -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 diff --git a/app/liquid_tags/spotify_tag.rb b/app/liquid_tags/spotify_tag.rb index bed4e213c..2c93be2c6 100644 --- a/app/liquid_tags/spotify_tag.rb +++ b/app/liquid_tags/spotify_tag.rb @@ -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 diff --git a/app/liquid_tags/stackblitz_tag.rb b/app/liquid_tags/stackblitz_tag.rb index 5fc2a600c..a071ae166 100644 --- a/app/liquid_tags/stackblitz_tag.rb +++ b/app/liquid_tags/stackblitz_tag.rb @@ -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 diff --git a/app/liquid_tags/stackery_tag.rb b/app/liquid_tags/stackery_tag.rb index 1fae8fdce..f439bf34f 100644 --- a/app/liquid_tags/stackery_tag.rb +++ b/app/liquid_tags/stackery_tag.rb @@ -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 diff --git a/app/liquid_tags/stackexchange_tag.rb b/app/liquid_tags/stackexchange_tag.rb index 2aa5bce1f..6941f164d 100644 --- a/app/liquid_tags/stackexchange_tag.rb +++ b/app/liquid_tags/stackexchange_tag.rb @@ -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 diff --git a/app/liquid_tags/tag_tag.rb b/app/liquid_tags/tag_tag.rb index c50c351fe..629a78b49 100644 --- a/app/liquid_tags/tag_tag.rb +++ b/app/liquid_tags/tag_tag.rb @@ -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 diff --git a/app/liquid_tags/tweet_tag.rb b/app/liquid_tags/tweet_tag.rb index fbf3983c8..1938af906 100644 --- a/app/liquid_tags/tweet_tag.rb +++ b/app/liquid_tags/tweet_tag.rb @@ -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 diff --git a/app/liquid_tags/twitch_tag.rb b/app/liquid_tags/twitch_tag.rb index 393db6f23..2a405f1d6 100644 --- a/app/liquid_tags/twitch_tag.rb +++ b/app/liquid_tags/twitch_tag.rb @@ -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") diff --git a/app/liquid_tags/twitter_timeline_tag.rb b/app/liquid_tags/twitter_timeline_tag.rb index cbd61a50d..fbb7f97b7 100644 --- a/app/liquid_tags/twitter_timeline_tag.rb +++ b/app/liquid_tags/twitter_timeline_tag.rb @@ -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 diff --git a/app/liquid_tags/user_subscription_tag.rb b/app/liquid_tags/user_subscription_tag.rb index a5802c9c2..352ee7827 100644 --- a/app/liquid_tags/user_subscription_tag.rb +++ b/app/liquid_tags/user_subscription_tag.rb @@ -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) diff --git a/app/liquid_tags/wikipedia_tag.rb b/app/liquid_tags/wikipedia_tag.rb index d8828a646..a85804605 100644 --- a/app/liquid_tags/wikipedia_tag.rb +++ b/app/liquid_tags/wikipedia_tag.rb @@ -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) diff --git a/app/liquid_tags/youtube_tag.rb b/app/liquid_tags/youtube_tag.rb index 6b90eff11..986f0ca9e 100644 --- a/app/liquid_tags/youtube_tag.rb +++ b/app/liquid_tags/youtube_tag.rb @@ -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] diff --git a/config/locales/en.yml b/config/locales/en.yml index 3d5fbb08f..601a9cf04 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -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" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 372a61661..1bb2ccc04 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -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" diff --git a/config/locales/liquid_tags/en.yml b/config/locales/liquid_tags/en.yml new file mode 100644 index 000000000..37f79e4a6 --- /dev/null +++ b/config/locales/liquid_tags/en.yml @@ -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 diff --git a/config/locales/liquid_tags/fr.yml b/config/locales/liquid_tags/fr.yml new file mode 100644 index 000000000..a1d0a7737 --- /dev/null +++ b/config/locales/liquid_tags/fr.yml @@ -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