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.
This commit is contained in:
derrod 2021-08-16 02:52:13 +02:00 committed by Rodney
parent 9b57490e2c
commit e212acf025
4 changed files with 68 additions and 36 deletions

View File

@ -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.<br>If the external browser does not open, follow this link and complete the authorization:<br>%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'.<br/>A detailed error description can be found at <a href='https://developers.google.com/youtube/v3/live/docs/errors'>https://developers.google.com/youtube/v3/live/docs/errors</a>"
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.<br>Your stream will stop and you will no longer be live."
# YouTube API errors in format "YouTube.Errors.<error reason>"
YouTube.Errors.liveStreamingNotEnabled="Live streaming is not enabled on the selected YouTube channel.<br/><br/>See <a href='https://www.youtube.com/features'>youtube.com/features</a> for more information."
YouTube.Errors.livePermissionBlocked="Live streaming is unavailable on the selected YouTube Channel.<br/>Please note that it may take up to 24 hours for live streaming to become available after enabling it in your channel settings.<br/><br/>See <a href='https://www.youtube.com/features'>youtube.com/features</a> for details."

View File

@ -135,7 +135,18 @@ OBSYoutubeActions::OBSYoutubeActions(QWidget *parent, Auth *auth)
qDeleteAll(ui->scrollAreaWidgetContents->findChildren<QWidget *>(
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("<big>%1 %2</big><br/>%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);
}
}

View File

@ -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<CategoryDescription> &category_list_out)
{
lastErrorMessage.clear();
lastErrorReason.clear();
const QString url_template = YOUTUBE_LIVE_VIDEOCATEGORIES_URL
"?part=snippet"
"&regionCode=%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();

View File

@ -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;
};