From e212acf025fa83d9f91eed2ceabd181ea3362fd5 Mon Sep 17 00:00:00 2001 From: derrod Date: Mon, 16 Aug 2021 02:52:13 +0200 Subject: [PATCH] UI: Improve YouTube (error) translatability Adds the ability to provide translated messages for YouTube API erorr reasons. Also adds translation for various internal errors that were previously hardcoded to english. Minor changes to existing translation strings to improve translatability. --- UI/data/locale/en-US.ini | 11 ++++++- UI/window-youtube-actions.cpp | 31 ++++++++++++------ UI/youtube-api-wrappers.cpp | 60 ++++++++++++++++++++--------------- UI/youtube-api-wrappers.hpp | 2 ++ 4 files changed, 68 insertions(+), 36 deletions(-) diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index bb7eed0cf..88f8ba3af 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -1179,8 +1179,10 @@ ContextBar.MediaControls.BlindSeek="Media Seek Widget" # YouTube Actions and Auth YouTube.Auth.Ok="Authorization completed successfully.\nYou can now close this page." YouTube.Auth.NoCode="The authorization process was not completed." +YouTube.Auth.NoChannels="No channel(s) available on selected account" YouTube.Auth.WaitingAuth.Title="YouTube User Authorization" YouTube.Auth.WaitingAuth.Text="Please complete the authorization in your external browser.
If the external browser does not open, follow this link and complete the authorization:
%1" +YouTube.AuthError.Text="Failed to get channel information: %1." YouTube.Actions.CreateNewEvent="Create a new event" YouTube.Actions.Title="Title*" @@ -1219,16 +1221,23 @@ YouTube.Actions.Error.General="YouTube access error. Please check your network c YouTube.Actions.Error.NoBroadcastCreated="Broadcast creation error '%1'.
A detailed error description can be found at https://developers.google.com/youtube/v3/live/docs/errors" YouTube.Actions.Error.NoStreamCreated="No stream created. Please relink your account." YouTube.Actions.Error.YouTubeApi="YouTube API Error. Please see the log file for more information." +YouTube.Actions.Error.BroadcastNotFound="The selected broadcast was not found." YouTube.Actions.EventCreated.Title="Event Created" YouTube.Actions.EventCreated.Text="Event successfully created." YouTube.Actions.ChooseEvent="Choose an Event" YouTube.Actions.Stream="Stream" -YouTube.Actions.Stream.ScheduledFor="scheduled for" +YouTube.Actions.Stream.ScheduledFor="Scheduled for %1" +YouTube.Actions.Stream.Resume="Resume interrupted stream" +YouTube.Actions.Stream.YTStudio="Automatically created by YouTube Studio" YouTube.Actions.Notify.Title="YouTube" YouTube.Actions.Notify.CreatingBroadcast="Creating a new Live Broadcast, please wait..." YouTube.Actions.AutoStartStreamingWarning="Auto start is disabled for this stream, you should click \"GO LIVE\"." YouTube.Actions.AutoStopStreamingWarning="You will not be able to reconnect.
Your stream will stop and you will no longer be live." + +# YouTube API errors in format "YouTube.Errors." +YouTube.Errors.liveStreamingNotEnabled="Live streaming is not enabled on the selected YouTube channel.

See youtube.com/features for more information." +YouTube.Errors.livePermissionBlocked="Live streaming is unavailable on the selected YouTube Channel.
Please note that it may take up to 24 hours for live streaming to become available after enabling it in your channel settings.

See youtube.com/features for details." diff --git a/UI/window-youtube-actions.cpp b/UI/window-youtube-actions.cpp index f17ccc860..5393d81b8 100644 --- a/UI/window-youtube-actions.cpp +++ b/UI/window-youtube-actions.cpp @@ -135,7 +135,18 @@ OBSYoutubeActions::OBSYoutubeActions(QWidget *parent, Auth *auth) qDeleteAll(ui->scrollAreaWidgetContents->findChildren( QString(), Qt::FindDirectChildrenOnly)); - connect(workerThread, &WorkerThread::failed, this, &QDialog::reject); + connect(workerThread, &WorkerThread::failed, this, [&]() { + auto last_error = apiYouTube->GetLastError(); + if (last_error.isEmpty()) + last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); + + if (!apiYouTube->GetTranslatedError(last_error)) + last_error = QTStr("YouTube.Actions.Error.Text") + .arg(last_error); + + ShowErrorDialog(this, last_error); + QDialog::reject(); + }); connect(workerThread, &WorkerThread::new_item, this, [&](const QString &title, const QString &dateTimeString, @@ -147,8 +158,8 @@ OBSYoutubeActions::OBSYoutubeActions(QWidget *parent, Auth *auth) QString("%1 %2
%3 %4") .arg(title, QTStr("YouTube.Actions.Stream"), - QTStr("YouTube.Actions.Stream.ScheduledFor"), - dateTimeString)); + QTStr("YouTube.Actions.Stream.ScheduledFor") + .arg(dateTimeString))); label->setAlignment(Qt::AlignHCenter); label->setMargin(4); @@ -380,8 +391,6 @@ bool OBSYoutubeActions::ChooseAnEventAction(YoutubeApiWrappers *api, } } - if (start) - api->StartBroadcast(selectedBroadcast); return true; } @@ -458,12 +467,14 @@ void OBSYoutubeActions::InitBroadcast() } else { // Fail. auto last_error = apiYouTube->GetLastError(); - if (last_error.isEmpty()) { + if (last_error.isEmpty()) last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - } - ShowErrorDialog( - this, QTStr("YouTube.Actions.Error.NoBroadcastCreated") - .arg(last_error)); + if (!apiYouTube->GetTranslatedError(last_error)) + last_error = + QTStr("YouTube.Actions.Error.NoBroadcastCreated") + .arg(last_error); + + ShowErrorDialog(this, last_error); } } diff --git a/UI/youtube-api-wrappers.cpp b/UI/youtube-api-wrappers.cpp index e76f42fd5..967c73446 100644 --- a/UI/youtube-api-wrappers.cpp +++ b/UI/youtube-api-wrappers.cpp @@ -41,6 +41,17 @@ bool IsYouTubeService(const std::string &service) return it != youtubeServices.end(); } +bool YoutubeApiWrappers::GetTranslatedError(QString &error_message) +{ + QString translated = + QTStr("YouTube.Errors." + lastErrorReason.toUtf8()); + // No translation found + if (translated.startsWith("YouTube.Errors.")) + return false; + error_message = translated; + return true; +} + YoutubeApiWrappers::YoutubeApiWrappers(const Def &d) : YoutubeAuth(d) {} bool YoutubeApiWrappers::TryInsertCommand(const char *url, @@ -136,6 +147,10 @@ bool YoutubeApiWrappers::InsertCommand(const char *url, error_code, url, json_out.dump().c_str()); lastError = json_out["error"]["code"].int_value(); + lastErrorReason = + QString(json_out["error"]["errors"][0]["reason"] + .string_value() + .c_str()); lastErrorMessage = QString( json_out["error"]["message"].string_value().c_str()); @@ -149,6 +164,7 @@ bool YoutubeApiWrappers::GetChannelDescription( ChannelDescription &channel_description) { lastErrorMessage.clear(); + lastErrorReason.clear(); const QByteArray url = YOUTUBE_LIVE_CHANNEL_URL "?part=snippet,contentDetails,statistics" "&mine=true"; @@ -158,8 +174,7 @@ bool YoutubeApiWrappers::GetChannelDescription( } if (json_out["pageInfo"]["totalResults"].int_value() == 0) { - lastErrorMessage = - "No channel(s) available on selected account"; + lastErrorMessage = QTStr("YouTube.Auth.NoChannels"); return false; } @@ -180,18 +195,8 @@ bool YoutubeApiWrappers::GetChannelDescription( bool YoutubeApiWrappers::InsertBroadcast(BroadcastDescription &broadcast) { - // Youtube API: The Title property's value must be between 1 and 100 characters long. - if (broadcast.title.isEmpty() || broadcast.title.length() > 100) { - blog(LOG_ERROR, "Insert broadcast FAIL: Wrong title."); - lastErrorMessage = "Broadcast title too long."; - return false; - } - // Youtube API: The property's value can contain up to 5000 characters. - if (broadcast.description.length() > 5000) { - blog(LOG_ERROR, "Insert broadcast FAIL: Description too long."); - lastErrorMessage = "Broadcast description too long."; - return false; - } + lastErrorMessage.clear(); + lastErrorReason.clear(); const QByteArray url = YOUTUBE_LIVE_BROADCAST_URL "?part=snippet,status,contentDetails"; const Json data = Json::object{ @@ -233,16 +238,8 @@ bool YoutubeApiWrappers::InsertBroadcast(BroadcastDescription &broadcast) bool YoutubeApiWrappers::InsertStream(StreamDescription &stream) { - // Youtube API documentation: The snippet.title property's value in the liveStream resource must be between 1 and 128 characters long. - if (stream.title.isEmpty() || stream.title.length() > 128) { - blog(LOG_ERROR, "Insert stream FAIL: wrong argument"); - return false; - } - // Youtube API: The snippet.description property's value in the liveStream resource can have up to 10000 characters. - if (stream.description.length() > 10000) { - blog(LOG_ERROR, "Insert stream FAIL: Description too long."); - return false; - } + lastErrorMessage.clear(); + lastErrorReason.clear(); const QByteArray url = YOUTUBE_LIVE_STREAM_URL "?part=snippet,cdn,status,contentDetails"; const Json data = Json::object{ @@ -273,6 +270,8 @@ bool YoutubeApiWrappers::InsertStream(StreamDescription &stream) bool YoutubeApiWrappers::BindStream(const QString broadcast_id, const QString stream_id) { + lastErrorMessage.clear(); + lastErrorReason.clear(); const QString url_template = YOUTUBE_LIVE_BROADCAST_BIND_URL "?id=%1" "&streamId=%2" @@ -288,6 +287,7 @@ bool YoutubeApiWrappers::BindStream(const QString broadcast_id, bool YoutubeApiWrappers::GetBroadcastsList(Json &json_out, QString page) { lastErrorMessage.clear(); + lastErrorReason.clear(); QByteArray url = YOUTUBE_LIVE_BROADCAST_URL "?part=snippet,contentDetails,status" "&broadcastType=all&maxResults=" DEFAULT_BROADCASTS_PER_QUERY @@ -301,6 +301,8 @@ bool YoutubeApiWrappers::GetVideoCategoriesList( const QString &country, const QString &language, QVector &category_list_out) { + lastErrorMessage.clear(); + lastErrorReason.clear(); const QString url_template = YOUTUBE_LIVE_VIDEOCATEGORIES_URL "?part=snippet" "®ionCode=%1" @@ -330,6 +332,8 @@ bool YoutubeApiWrappers::SetVideoCategory(const QString &video_id, const QString &video_description, const QString &categorie_id) { + lastErrorMessage.clear(); + lastErrorReason.clear(); const QByteArray url = YOUTUBE_LIVE_VIDEOS_URL "?part=snippet"; const Json data = Json::object{ {"id", QT_TO_UTF8(video_id)}, @@ -348,6 +352,7 @@ bool YoutubeApiWrappers::SetVideoCategory(const QString &video_id, bool YoutubeApiWrappers::StartBroadcast(const QString &broadcast_id) { lastErrorMessage.clear(); + lastErrorReason.clear(); if (!ResetBroadcast(broadcast_id)) return false; @@ -370,6 +375,7 @@ bool YoutubeApiWrappers::StartLatestBroadcast() bool YoutubeApiWrappers::StopBroadcast(const QString &broadcast_id) { lastErrorMessage.clear(); + lastErrorReason.clear(); const QString url_template = YOUTUBE_LIVE_BROADCAST_TRANSITION_URL "?id=%1" @@ -389,6 +395,7 @@ bool YoutubeApiWrappers::StopLatestBroadcast() bool YoutubeApiWrappers::ResetBroadcast(const QString &broadcast_id) { lastErrorMessage.clear(); + lastErrorReason.clear(); const QString url_template = YOUTUBE_LIVE_BROADCAST_URL "?part=id,snippet,contentDetails,status" @@ -448,6 +455,7 @@ bool YoutubeApiWrappers::FindBroadcast(const QString &id, json11::Json &json_out) { lastErrorMessage.clear(); + lastErrorReason.clear(); QByteArray url = YOUTUBE_LIVE_BROADCAST_URL "?part=id,snippet,contentDetails,status" "&broadcastType=all&maxResults=1"; @@ -458,7 +466,8 @@ bool YoutubeApiWrappers::FindBroadcast(const QString &id, auto items = json_out["items"].array_items(); if (items.size() != 1) { - lastErrorMessage = "No active broadcast found."; + lastErrorMessage = + QTStr("YouTube.Actions.Error.BroadcastNotFound"); return false; } @@ -468,6 +477,7 @@ bool YoutubeApiWrappers::FindBroadcast(const QString &id, bool YoutubeApiWrappers::FindStream(const QString &id, json11::Json &json_out) { lastErrorMessage.clear(); + lastErrorReason.clear(); QByteArray url = YOUTUBE_LIVE_STREAM_URL "?part=id,snippet,cdn,status" "&maxResults=1"; url += "&id=" + id.toUtf8(); diff --git a/UI/youtube-api-wrappers.hpp b/UI/youtube-api-wrappers.hpp index 317691f25..b7123b5e4 100644 --- a/UI/youtube-api-wrappers.hpp +++ b/UI/youtube-api-wrappers.hpp @@ -83,10 +83,12 @@ public: bool FindStream(const QString &id, json11::Json &json_out); QString GetLastError() { return lastErrorMessage; }; + bool GetTranslatedError(QString &error_message); private: QString broadcast_id; int lastError; QString lastErrorMessage; + QString lastErrorReason; };