mirror of https://github.com/procxx/kepka.git
Support new plural keys format.
All the old plural phrases were changed to work with the new format.
This commit is contained in:
parent
b6046d829f
commit
85e6f55536
|
@ -88,7 +88,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_continue" = "Continue";
|
||||
"lng_close" = "Close";
|
||||
"lng_connecting" = "Connecting...";
|
||||
"lng_reconnecting" = "Reconnect {count:now|in # s|in # s}...";
|
||||
"lng_reconnecting#one" = "Reconnect in {count} s...";
|
||||
"lng_reconnecting#other" = "Reconnect in {count} s...";
|
||||
"lng_reconnecting_try_now" = "Try now";
|
||||
|
||||
"lng_status_service_notifications" = "service notifications";
|
||||
|
@ -102,8 +103,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_status_last_month" = "last seen within a month";
|
||||
"lng_status_invisible" = "invisible";
|
||||
"lng_status_lastseen_now" = "last seen just now";
|
||||
"lng_status_lastseen_minutes" = "last seen {count:_not_used_|# minute|# minutes} ago";
|
||||
"lng_status_lastseen_hours" = "last seen {count:_not_used_|# hour|# hours} ago";
|
||||
"lng_status_lastseen_minutes#one" = "last seen {count} minute ago";
|
||||
"lng_status_lastseen_minutes#other" = "last seen {count} minutes ago";
|
||||
"lng_status_lastseen_hours#one" = "last seen {count} hour ago";
|
||||
"lng_status_lastseen_hours#other" = "last seen {count} hours ago";
|
||||
"lng_status_lastseen_today" = "last seen today at {time}";
|
||||
"lng_status_lastseen_yesterday" = "last seen yesterday at {time}";
|
||||
"lng_status_lastseen_date" = "last seen {date}";
|
||||
|
@ -112,14 +115,20 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_status_connecting" = "connecting...";
|
||||
|
||||
"lng_chat_status_unaccessible" = "group is inaccessible";
|
||||
"lng_chat_status_members" = "{count:no members|# member|# members}";
|
||||
"lng_chat_status_members_online" = "{count:_not_used_|# member|# members}, {count_online:_not_used_|# online|# online}";
|
||||
"lng_chat_status_no_members" = "no members";
|
||||
"lng_chat_status_members#one" = "{count} member";
|
||||
"lng_chat_status_members#other" = "{count} members";
|
||||
"lng_chat_status_online#one" = "{count} online";
|
||||
"lng_chat_status_online#other" = "{count} online";
|
||||
"lng_chat_status_members_online" = "{members_count}, {online_count}";
|
||||
|
||||
"lng_channel_status" = "channel";
|
||||
"lng_group_status" = "group";
|
||||
|
||||
"lng_channel_members_link" = "{count:_not_used_|# member|# members}";
|
||||
"lng_channel_admins_link" = "{count:_not_used_|# administrator|# administrators}";
|
||||
"lng_channel_members_link#one" = "{count} member";
|
||||
"lng_channel_members_link#other" = "{count} members";
|
||||
"lng_channel_admins_link#one" = "{count} administrator";
|
||||
"lng_channel_admins_link#other" = "{count} administrators";
|
||||
|
||||
"lng_server_error" = "Internal server error.";
|
||||
"lng_flood_error" = "Too many tries. Please try again later.";
|
||||
|
@ -128,7 +137,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_join_channel_error" = "Sorry, you have joined too many channels and supergroups. Please leave some before joining.";
|
||||
"lng_error_phone_flood" = "Sorry, you have deleted and re-created your account too many times recently. Please wait for a few days before signing up again.";
|
||||
"lng_error_start_minimized_passcoded" = "You have set a local passcode, so the app can't be launched minimized. App will ask you to enter the passcode before it can start working.";
|
||||
"lng_error_pinned_max" = "Sorry, you can pin no more than {count:_not_used_|# chat|# chats} to the top.";
|
||||
"lng_error_pinned_max#one" = "Sorry, you can pin no more than {count} chat to the top.";
|
||||
"lng_error_pinned_max#other" = "Sorry, you can pin no more than {count} chats to the top.";
|
||||
"lng_error_public_groups_denied" = "Unfortunately, you were banned from participating in public groups.\n{more_info}";
|
||||
|
||||
"lng_edit_deleted" = "This message was deleted";
|
||||
|
@ -198,9 +208,14 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_signin_sure_reset" = "Warning!\n\nYou will lose all your chats and messages, along with any media and files you shared!\n\nDo you want to reset your account?";
|
||||
"lng_signin_reset" = "Reset";
|
||||
"lng_signin_reset_wait" = "Since the account {phone_number} is active and protected by a password, we will delete it in 1 week for security purposes. You can cancel this process at any time.\n\nYou’ll be able to reset your account in:\n{when}";
|
||||
"lng_signin_reset_in_days" = "{count_days:0 days|# day|# days} {count_hours:0 hours|# hour|# hours} {count_minutes:0 minutes|# minute|# minutes}";
|
||||
"lng_signin_reset_in_hours" = "{count_hours:0 hours|# hour|# hours} {count_minutes:0 minutes|# minute|# minutes}";
|
||||
"lng_signin_reset_in_minutes" = "{count_minutes:0 minutes|# minute|# minutes}";
|
||||
"lng_signin_reset_days#one" = "{count} day";
|
||||
"lng_signin_reset_days#other" = "{count} days";
|
||||
"lng_signin_reset_hours#one" = "{count} hour";
|
||||
"lng_signin_reset_hours#other" = "{count} hours";
|
||||
"lng_signin_reset_minutes#one" = "{count} minute";
|
||||
"lng_signin_reset_minutes#other" = "{count} minutes";
|
||||
"lng_signin_reset_in_days" = "{days_count} {hours_count} {minutes_count}";
|
||||
"lng_signin_reset_in_hours" = "{hours_count} {minutes_count}";
|
||||
"lng_signin_reset_cancelled" = "Your recent attempts to reset this account have been cancelled by its active user. Please try again in 7 days.";
|
||||
|
||||
"lng_signup_title" = "Your Info";
|
||||
|
@ -303,7 +318,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
|
||||
"lng_backgrounds_header" = "Choose your new chat background";
|
||||
"lng_theme_sure_keep" = "Keep this color theme?";
|
||||
"lng_theme_reverting" = "Reverting to the old color theme in {count:_not_used_|# second|# seconds}.";
|
||||
"lng_theme_reverting#one" = "Reverting to the old color theme in {count} second.";
|
||||
"lng_theme_reverting#other" = "Reverting to the old color theme in {count} seconds.";
|
||||
"lng_theme_keep_changes" = "Keep changes";
|
||||
"lng_theme_revert" = "Revert";
|
||||
|
||||
|
@ -329,8 +345,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
|
||||
"lng_local_storage_title" = "Local storage";
|
||||
"lng_settings_no_data_cached" = "No cached data found!";
|
||||
"lng_settings_images_cached" = "{count:_not_used_|# image|# images}, {size}";
|
||||
"lng_settings_audios_cached" = "{count:_not_used_|# voice message|# voice messages}, {size}";
|
||||
"lng_settings_images_cached#one" = "{count} image, {size}";
|
||||
"lng_settings_images_cached#other" = "{count} images, {size}";
|
||||
"lng_settings_audios_cached#one" = "{count} voice message, {size}";
|
||||
"lng_settings_audios_cached#other" = "{count} voice messages, {size}";
|
||||
"lng_local_storage_clear" = "Clear all";
|
||||
"lng_local_storage_clearing" = "Clearing...";
|
||||
"lng_local_storage_cleared" = "Cleared!";
|
||||
|
@ -348,8 +366,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_passcode_autolock" = "Auto-Lock";
|
||||
"lng_passcode_autolock_away" = "Auto-Lock if away for:";
|
||||
"lng_passcode_autolock_inactive" = "Auto-Lock if inactive for:";
|
||||
"lng_passcode_autolock_minutes" = "{count:_not_used_|# minute|# minutes}";
|
||||
"lng_passcode_autolock_hours" = "{count:_not_used_|# hour|# hours}";
|
||||
"lng_passcode_autolock_minutes#one" = "{count} minute";
|
||||
"lng_passcode_autolock_minutes#other" = "{count} minutes";
|
||||
"lng_passcode_autolock_hours#one" = "{count} hour";
|
||||
"lng_passcode_autolock_hours#other" = "{count} hours";
|
||||
"lng_passcode_enter_old" = "Enter current passcode";
|
||||
"lng_passcode_enter_first" = "Enter a passcode";
|
||||
"lng_passcode_enter_new" = "Enter new passcode";
|
||||
|
@ -455,32 +475,46 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_edit_privacy_lastseen_title" = "Last seen privacy";
|
||||
"lng_edit_privacy_lastseen_description" = "You can choose who can see your last seen time:";
|
||||
"lng_edit_privacy_lastseen_warning" = "Important: you won't be able to see Last Seen times for people with whom you don't share your Last Seen time. Approximate last seen will be shown instead (recently, within a week, within a month).";
|
||||
"lng_edit_privacy_lastseen_always" = "Always share with{count:| # user| # users}";
|
||||
"lng_edit_privacy_lastseen_never" = "Never share with{count:| # user| # users}";
|
||||
"lng_edit_privacy_lastseen_always_empty" = "Always share with";
|
||||
"lng_edit_privacy_lastseen_always#one" = "Always share with {count} user";
|
||||
"lng_edit_privacy_lastseen_always#other" = "Always share with {count} users";
|
||||
"lng_edit_privacy_lastseen_never_empty" = "Never share with";
|
||||
"lng_edit_privacy_lastseen_never#one" = "Never share with {count} user";
|
||||
"lng_edit_privacy_lastseen_never#other" = "Never share with {count} users";
|
||||
"lng_edit_privacy_lastseen_exceptions" = "These settings will override the values above.";
|
||||
"lng_edit_privacy_lastseen_always_title" = "Always share with";
|
||||
"lng_edit_privacy_lastseen_never_title" = "Never share with";
|
||||
|
||||
"lng_edit_privacy_groups_title" = "Group invite settings";
|
||||
"lng_edit_privacy_groups_description" = "You can choose who can add you to groups and channels with granular precision:";
|
||||
"lng_edit_privacy_groups_always" = "Always allow{count:| # user| # users}";
|
||||
"lng_edit_privacy_groups_never" = "Never allow{count:| # user| # users}";
|
||||
"lng_edit_privacy_groups_always_empty" = "Always allow";
|
||||
"lng_edit_privacy_groups_always#one" = "Always allow {count} user";
|
||||
"lng_edit_privacy_groups_always#other" = "Always allow {count} users";
|
||||
"lng_edit_privacy_groups_never_empty" = "Never allow";
|
||||
"lng_edit_privacy_groups_never#one" = "Never allow {count} user";
|
||||
"lng_edit_privacy_groups_never#other" = "Never allow {count} users";
|
||||
"lng_edit_privacy_groups_exceptions" = "These users will or will not be able to add you to groups and channels regardless of the settings above.";
|
||||
"lng_edit_privacy_groups_always_title" = "Always allow";
|
||||
"lng_edit_privacy_groups_never_title" = "Never allow";
|
||||
|
||||
"lng_edit_privacy_calls_title" = "Phone calls privacy";
|
||||
"lng_edit_privacy_calls_description" = "You can restrict who can call you:";
|
||||
"lng_edit_privacy_calls_always" = "Always allow{count:| # user| # users}";
|
||||
"lng_edit_privacy_calls_never" = "Never allow{count:| # user| # users}";
|
||||
"lng_edit_privacy_calls_always_empty" = "Always allow";
|
||||
"lng_edit_privacy_calls_always#one" = "Always allow {count} user";
|
||||
"lng_edit_privacy_calls_always#other" = "Always allow {count} users";
|
||||
"lng_edit_privacy_calls_never_empty" = "Never allow";
|
||||
"lng_edit_privacy_calls_never#one" = "Never allow {count} user";
|
||||
"lng_edit_privacy_calls_never#other" = "Never allow {count} users";
|
||||
"lng_edit_privacy_calls_exceptions" = "These users will or will not be able to call you regardless of the settings above.";
|
||||
"lng_edit_privacy_calls_always_title" = "Always allow";
|
||||
"lng_edit_privacy_calls_never_title" = "Never allow";
|
||||
|
||||
"lng_self_destruct_title" = "Account self-destruction";
|
||||
"lng_self_destruct_description" = "If you don't come online at least once within this period, your account will be deleted along with all groups, messages and contacts.";
|
||||
"lng_self_destruct_months" = "{count:_not_used_|# month|# months}";
|
||||
"lng_self_destruct_years" = "{count:_not_used_|# year|# years}";
|
||||
"lng_self_destruct_months#one" = "{count} month";
|
||||
"lng_self_destruct_months#other" = "{count} months";
|
||||
"lng_self_destruct_years#one" = "{count} year";
|
||||
"lng_self_destruct_years#other" = "{count} years";
|
||||
|
||||
"lng_change_phone_title" = "Change phone number";
|
||||
"lng_change_phone_description" = "You can change your Telegram number\nhere. Your account and all your cloud data\n— messages, media, contacts, etc. will be\nmoved to the new number.\n\n[b]Important[/b]: all your Telegram contacts will\nget your [b]new number[/b] added to their address\nbook, provided they had your old number and\nyou haven't blocked them in Telegram.";
|
||||
|
@ -508,7 +542,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_profile_edit_public_link" = "Edit public link";
|
||||
"lng_profile_manage_admins" = "Manage administrators";
|
||||
"lng_profile_manage_blocklist" = "Manage blocked users";
|
||||
"lng_profile_common_groups" = "{count:_not_used_|# group|# groups} in common";
|
||||
"lng_profile_common_groups#one" = "{count} group in common";
|
||||
"lng_profile_common_groups#other" = "{count} groups in common";
|
||||
"lng_profile_common_groups_section" = "Groups in common";
|
||||
"lng_profile_participants_section" = "Members";
|
||||
"lng_profile_info_section" = "Info";
|
||||
|
@ -548,17 +583,23 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_profile_loading" = "Loading...";
|
||||
"lng_profile_shared_media" = "Shared media";
|
||||
"lng_profile_no_media" = "No media in this conversation.";
|
||||
"lng_profile_photos" = "{count:_not_used_|# photo|# photos}";
|
||||
"lng_profile_photos#one" = "{count} photo";
|
||||
"lng_profile_photos#other" = "{count} photos";
|
||||
"lng_profile_photos_header" = "Photos";
|
||||
"lng_profile_videos" = "{count:_not_used_|# video|# videos}";
|
||||
"lng_profile_videos#one" = "{count} video";
|
||||
"lng_profile_videos#other" = "{count} videos";
|
||||
"lng_profile_videos_header" = "Videos";
|
||||
"lng_profile_songs" = "{count:_not_used_|# audio file|# audio files}";
|
||||
"lng_profile_songs#one" = "{count} audio file";
|
||||
"lng_profile_songs#other" = "{count} audio files";
|
||||
"lng_profile_songs_header" = "Audio files";
|
||||
"lng_profile_files" = "{count:_not_used_|# file|# files}";
|
||||
"lng_profile_files#one" = "{count} file";
|
||||
"lng_profile_files#other" = "{count} files";
|
||||
"lng_profile_files_header" = "Files";
|
||||
"lng_profile_audios" = "{count:_not_used_|# voice message|# voice messages}";
|
||||
"lng_profile_audios#one" = "{count} voice message";
|
||||
"lng_profile_audios#other" = "{count} voice messages";
|
||||
"lng_profile_audios_header" = "Voice messages";
|
||||
"lng_profile_shared_links" = "{count:_not_used_|# shared link|# shared links}";
|
||||
"lng_profile_shared_links#one" = "{count} shared link";
|
||||
"lng_profile_shared_links#other" = "{count} shared links";
|
||||
"lng_profile_shared_links_header" = "Shared links";
|
||||
"lng_profile_copy_phone" = "Copy Phone Number";
|
||||
"lng_profile_copy_fullname" = "Copy Name";
|
||||
|
@ -581,7 +622,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_channel_add_admins" = "New administrator";
|
||||
"lng_channel_add_members" = "Add members";
|
||||
"lng_channel_members" = "Members";
|
||||
"lng_channel_only_last_shown" = "Only the last {count:_not_used_|# member is|# members are} shown here";
|
||||
"lng_channel_only_last_shown#one" = "Only the last {count} member is shown here";
|
||||
"lng_channel_only_last_shown#other" = "Only the last {count} members are shown here";
|
||||
"lng_channel_admins" = "Administrators";
|
||||
"lng_channel_add_admin" = "Add Administrator";
|
||||
"lng_channel_admin_sure" = "Add {user} to administrators?";
|
||||
|
@ -595,7 +637,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
|
||||
"lng_participant_filter" = "Search";
|
||||
"lng_participant_invite" = "Invite";
|
||||
"lng_participant_invite_sorry" = "Sorry, you can only add the first {count:_not_used|# member|# members} to a channel personally.\n\nFrom now on, people will need to join via your invite link.";
|
||||
"lng_participant_invite_sorry#one" = "Sorry, you can only add the first {count} member to a channel personally.\n\nFrom now on, people will need to join via your invite link.";
|
||||
"lng_participant_invite_sorry#other" = "Sorry, you can only add the first {count} members to a channel personally.\n\nFrom now on, people will need to join via your invite link.";
|
||||
"lng_create_group_back" = "Back";
|
||||
"lng_create_group_next" = "Next";
|
||||
"lng_create_group_create" = "Create";
|
||||
|
@ -637,8 +680,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_message_empty" = "Empty Message";
|
||||
"lng_message_unsupported" = "This message is not supported by your version of Telegram Desktop. Please update to the last version in Settings or install it from {link}";
|
||||
|
||||
"lng_duration_seconds" = "{count:_not_used_|# second|# seconds}";
|
||||
"lng_duration_minutes_seconds" = "{count_minutes:# min|# min|# min} {count_seconds:# sec|# sec|# sec}";
|
||||
"lng_duration_seconds#one" = "{count} second";
|
||||
"lng_duration_seconds#other" = "{count} seconds";
|
||||
"lng_duration_minsec_minutes#one" = "{count} min";
|
||||
"lng_duration_minsec_minutes#other" = "{count} min";
|
||||
"lng_duration_minsec_seconds#one" = "{count} sec";
|
||||
"lng_duration_minsec_seconds#other" = "{count} sec";
|
||||
"lng_duration_minutes_seconds" = "{minutes_count} {seconds_count}";
|
||||
|
||||
"lng_action_add_user" = "{from} added {user}";
|
||||
"lng_action_add_users_many" = "{from} added {users}";
|
||||
|
@ -676,18 +724,24 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_action_pinned_media_sticker" = "a sticker";
|
||||
"lng_action_pinned_media_emoji_sticker" = "a {emoji} sticker";
|
||||
"lng_action_pinned_media_game" = "the game «{game}»";
|
||||
"lng_action_game_score" = "{from} scored {count:#|#|#} in {game}";
|
||||
"lng_action_game_you_scored" = "You scored {count:#|#|#} in {game}";
|
||||
"lng_action_game_score_no_game" = "{from} scored {count:#|#|#}";
|
||||
"lng_action_game_you_scored_no_game" = "You scored {count:#|#|#}";
|
||||
"lng_action_game_score#one" = "{from} scored {count} in {game}";
|
||||
"lng_action_game_score#other" = "{from} scored {count} in {game}";
|
||||
"lng_action_game_you_scored#one" = "You scored {count} in {game}";
|
||||
"lng_action_game_you_scored#other" = "You scored {count} in {game}";
|
||||
"lng_action_game_score_no_game#one" = "{from} scored {count}";
|
||||
"lng_action_game_score_no_game#other" = "{from} scored {count}";
|
||||
"lng_action_game_you_scored_no_game#one" = "You scored {count}";
|
||||
"lng_action_game_you_scored_no_game#other" = "You scored {count}";
|
||||
"lng_action_payment_done" = "You have just successfully transferred {amount} to {user}";
|
||||
"lng_action_payment_done_for" = "You have just successfully transferred {amount} to {user} for {invoice}";
|
||||
|
||||
"lng_profile_migrate_reached" = "{count:_not_used_|# member|# members} limit reached";
|
||||
"lng_profile_migrate_reached#one" = "{count} member limit reached";
|
||||
"lng_profile_migrate_reached#other" = "{count} members limit reached";
|
||||
"lng_profile_migrate_body" = "To get over this limit, you can upgrade your group to a supergroup.";
|
||||
"lng_profile_migrate_learn_more" = "Learn more »";
|
||||
"lng_profile_migrate_about" = "If you'd like to go over this limit, you can upgrade your group to a supergroup. In supergroups:";
|
||||
"lng_profile_migrate_feature1" = "— The members limit is {count:_not_used_|# user|# users}";
|
||||
"lng_profile_migrate_feature1#one" = "— The members limit is {count} user";
|
||||
"lng_profile_migrate_feature1#other" = "— The members limit is {count} users";
|
||||
"lng_profile_migrate_feature2" = "— New members see the entire chat history";
|
||||
"lng_profile_migrate_feature3" = "— Admins delete messages for everyone";
|
||||
"lng_profile_migrate_feature4" = "— Notifications are muted by default";
|
||||
|
@ -702,7 +756,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_profile_convert_feature4" = "— Creator can set a public link for the group";
|
||||
"lng_profile_convert_warning" = "{bold_start}Note:{bold_end} This action can not be undone";
|
||||
"lng_profile_convert_confirm" = "Convert";
|
||||
"lng_profile_add_more_after_upgrade" = "You will be able to add up to {count:_not_used_|# member|# members} after you upgrade your group to a supergroup.";
|
||||
"lng_profile_add_more_after_upgrade#one" = "You will be able to add up to {count} member after you upgrade your group to a supergroup.";
|
||||
"lng_profile_add_more_after_upgrade#other" = "You will be able to add up to {count} members after you upgrade your group to a supergroup.";
|
||||
|
||||
"lng_channel_not_accessible" = "Sorry, this channel is not accessible.";
|
||||
"lng_group_not_accessible" = "Sorry, this group is not accessible.";
|
||||
|
@ -717,7 +772,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_group_invite_want_join_channel" = "Do you want to join the channel «{title}»?";
|
||||
"lng_group_invite_join" = "Join";
|
||||
|
||||
"lng_group_invite_members" = "{count:_not_used_|# member|# members}, among them:";
|
||||
"lng_group_invite_members#one" = "{count} member, among them:";
|
||||
"lng_group_invite_members#other" = "{count} members, among them:";
|
||||
|
||||
"lng_group_invite_link" = "Invite link:";
|
||||
"lng_group_invite_create" = "Create an invite link";
|
||||
|
@ -810,7 +866,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_stickers_you_have" = "Manage and reorder sticker packs";
|
||||
"lng_stickers_featured" = "Trending Stickers";
|
||||
"lng_stickers_return" = "Undo";
|
||||
"lng_stickers_count" = "{count:Loading...|# sticker|# stickers}";
|
||||
"lng_stickers_count#one" = "{count} sticker";
|
||||
"lng_stickers_count#other" = "{count} stickers";
|
||||
"lng_stickers_masks_pack" = "This is a pack of mask stickers. You can use them in the photo editor on our mobile apps.";
|
||||
|
||||
"lng_in_dlg_photo" = "Photo";
|
||||
|
@ -885,11 +942,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_typing" = "typing";
|
||||
"lng_user_typing" = "{user} is typing";
|
||||
"lng_users_typing" = "{user} and {second_user} are typing";
|
||||
"lng_many_typing" = "{count:_not_used_|# is|# are} typing";
|
||||
"lng_many_typing#one" = "{count} is typing";
|
||||
"lng_many_typing#other" = "{count} are typing";
|
||||
"lng_playing_game" = "playing a game";
|
||||
"lng_user_playing_game" = "{user} is playing a game";
|
||||
"lng_users_playing_game" = "{user} and {second_user} are playing a game";
|
||||
"lng_many_playing_game" = "{count:_not_used_|# is|# are} playing a game";
|
||||
"lng_many_playing_game#one" = "{count} is playing a game";
|
||||
"lng_many_playing_game#other" = "{count} are playing a game";
|
||||
"lng_send_action_record_video" = "recording a video";
|
||||
"lng_user_action_record_video" = "{user} is recording a video";
|
||||
"lng_send_action_upload_video" = "sending a video";
|
||||
|
@ -910,7 +969,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_user_action_geo_location" = "{user} is picking a location";
|
||||
"lng_send_action_choose_contact" = "choosing a contact";
|
||||
"lng_user_action_choose_contact" = "{user} is choosing a contact";
|
||||
"lng_unread_bar" = "{count:_not_used_|# unread message|# unread messages}";
|
||||
"lng_unread_bar#one" = "{count} unread message";
|
||||
"lng_unread_bar#other" = "{count} unread messages";
|
||||
|
||||
"lng_maps_point" = "Location";
|
||||
"lng_save_photo" = "Save image";
|
||||
|
@ -977,15 +1037,20 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_really_send_image" = "Do you want to send this image?";
|
||||
"lng_really_send_file" = "Do you want to send this file?";
|
||||
"lng_really_share_contact" = "Do you want to share this contact?";
|
||||
"lng_send_images_compress" = "Compress {count:_not_used_|image|images}";
|
||||
"lng_send_images_compress#one" = "Compress image";
|
||||
"lng_send_images_compress#other" = "Compress images";
|
||||
"lng_send_image_non_local" = "Could not send a non local file: {name}";
|
||||
"lng_send_image_empty" = "Could not send an empty file: {name}";
|
||||
"lng_send_image_too_large" = "Could not send a file, because it is larger than 1500 MB: {name}";
|
||||
"lng_send_folder" = "Could not send «{name}» because it is a directory :(";
|
||||
"lng_send_images_selected" = "{count:_not_used_|# image|# images} selected";
|
||||
"lng_send_photos" = "Send {count:_not_used_|# photo|# photos}";
|
||||
"lng_send_files_selected" = "{count:_not_used_|# file|# files} selected";
|
||||
"lng_send_files" = "Send {count:_not_used_|# file|# files}";
|
||||
"lng_send_images_selected#one" = "{count} image selected";
|
||||
"lng_send_images_selected#other" = "{count} images selected";
|
||||
"lng_send_photos#one" = "Send {count} photo";
|
||||
"lng_send_photos#other" = "Send {count} photos";
|
||||
"lng_send_files_selected#one" = "{count} file selected";
|
||||
"lng_send_files_selected#other" = "{count} files selected";
|
||||
"lng_send_files#one" = "Send {count} file";
|
||||
"lng_send_files#other" = "Send {count} files";
|
||||
|
||||
"lng_forward_choose" = "Choose recipient...";
|
||||
"lng_forward_cant" = "Sorry, no way to forward here :(";
|
||||
|
@ -994,8 +1059,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_forward_send_file_confirm" = "Send «{name}» to {recipient}?";
|
||||
"lng_forward_send_files_confirm" = "Send selected files to {recipient}?";
|
||||
"lng_forward_send" = "Send";
|
||||
"lng_forward_messages" = "{count:_not_used_|Forwarded message|# forwarded messages}";
|
||||
"lng_forwarding_from" = "{user} and {count:_not_used_|# other|# others}";
|
||||
"lng_forward_messages#one" = "{count} forwarded message";
|
||||
"lng_forward_messages#other" = "{count} forwarded messages";
|
||||
"lng_forwarding_from#one" = "{user} and {count} other";
|
||||
"lng_forwarding_from#other" = "{user} and {count} others";
|
||||
"lng_forwarding_from_two" = "{user} and {second_user}";
|
||||
"lng_inline_switch_choose" = "Choose conversation...";
|
||||
"lng_inline_switch_cant" = "Sorry, no way to write here :(";
|
||||
|
@ -1041,15 +1108,20 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_selected_clear" = "Cancel";
|
||||
"lng_selected_delete" = "Delete";
|
||||
"lng_selected_forward" = "Forward";
|
||||
"lng_selected_count" = "{count:_not_used_|# message|# messages}";
|
||||
"lng_selected_count#one" = "{count} message";
|
||||
"lng_selected_count#other" = "{count} messages";
|
||||
"lng_selected_cancel_sure_this" = "Cancel uploading?";
|
||||
"lng_selected_upload_stop" = "Stop";
|
||||
"lng_selected_delete_sure_this" = "Do you want to delete this message?";
|
||||
"lng_selected_delete_sure" = "Do you want to delete {count:_not_used_|# message|# messages}?";
|
||||
"lng_selected_delete_sure#one" = "Do you want to delete {count} message?";
|
||||
"lng_selected_delete_sure#other" = "Do you want to delete {count} messages?";
|
||||
"lng_delete_photo_sure" = "Do you want to delete this photo?";
|
||||
"lng_delete_for_everyone_hint" = "This will delete {count:_not_used_|it|them} for everyone in this chat.";
|
||||
"lng_delete_for_me_chat_hint" = "This will delete {count:_not_used_|it|them} just for you, not for other participants of the chat.";
|
||||
"lng_delete_for_me_hint" = "This will delete {count:_not_used_|it|them} just for you.";
|
||||
"lng_delete_for_everyone_hint#one" = "This will delete it for everyone in this chat.";
|
||||
"lng_delete_for_everyone_hint#other" = "This will delete them for everyone in this chat.";
|
||||
"lng_delete_for_me_chat_hint#one" = "This will delete it just for you, not for other participants of the chat.";
|
||||
"lng_delete_for_me_chat_hint#other" = "This will delete them just for you, not for other participants of the chat.";
|
||||
"lng_delete_for_me_hint#one" = "This will delete it just for you.";
|
||||
"lng_delete_for_me_hint#other" = "This will delete them just for you.";
|
||||
"lng_delete_for_everyone_check" = "Delete for everyone";
|
||||
"lng_delete_for_other_check" = "Delete for {user}";
|
||||
"lng_box_delete" = "Delete";
|
||||
|
@ -1061,7 +1133,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_about_text_3" = "Visit {faq_open}Telegram FAQ{faq_close} for more info.";
|
||||
"lng_about_done" = "Done";
|
||||
|
||||
"lng_search_found_results" = "{count:No messages found|Found # message|Found # messages}";
|
||||
"lng_search_no_results" = "No messages found";
|
||||
"lng_search_found_results#one" = "Found {count} message";
|
||||
"lng_search_found_results#other" = "Found {count} messages";
|
||||
"lng_search_global_results" = "Global search results";
|
||||
|
||||
"lng_media_save_progress" = "{ready} of {total} {mb}";
|
||||
|
|
|
@ -547,14 +547,18 @@ void DeleteMessagesBox::deleteAndClear() {
|
|||
Ui::hideLayer();
|
||||
}
|
||||
|
||||
ConfirmInviteBox::ConfirmInviteBox(QWidget*, const QString &title, const MTPChatPhoto &photo, int count, const QVector<UserData*> &participants)
|
||||
ConfirmInviteBox::ConfirmInviteBox(QWidget*, const QString &title, bool isChannel, const MTPChatPhoto &photo, int count, const QVector<UserData*> &participants)
|
||||
: _title(this, st::confirmInviteTitle)
|
||||
, _status(this, st::confirmInviteStatus)
|
||||
, _participants(participants) {
|
||||
_title->setText(title);
|
||||
QString status;
|
||||
if (_participants.isEmpty() || _participants.size() >= count) {
|
||||
status = lng_chat_status_members(lt_count, count);
|
||||
if (count > 0) {
|
||||
status = lng_chat_status_members(lt_count, count);
|
||||
} else {
|
||||
status = lang(isChannel ? lng_channel_status : lng_group_status);
|
||||
}
|
||||
} else {
|
||||
status = lng_group_invite_members(lt_count, count);
|
||||
}
|
||||
|
|
|
@ -200,7 +200,7 @@ private:
|
|||
|
||||
class ConfirmInviteBox : public BoxContent, public RPCSender {
|
||||
public:
|
||||
ConfirmInviteBox(QWidget*, const QString &title, const MTPChatPhoto &photo, int count, const QVector<UserData*> &participants);
|
||||
ConfirmInviteBox(QWidget*, const QString &title, bool isChannel, const MTPChatPhoto &photo, int count, const QVector<UserData*> &participants);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
|
|
@ -898,11 +898,13 @@ ContactsBox::Inner::ContactData *ContactsBox::Inner::contactData(Dialogs::Row *r
|
|||
data->statusText = App::onlineText(peer->asUser(), _time);
|
||||
data->statusHasOnlineColor = App::onlineColorUse(peer->asUser(), _time);
|
||||
} else if (peer->isChat()) {
|
||||
ChatData *chat = peer->asChat();
|
||||
auto chat = peer->asChat();
|
||||
if (!chat->amIn()) {
|
||||
data->statusText = lang(lng_chat_status_unaccessible);
|
||||
} else {
|
||||
} else if (chat->count > 0) {
|
||||
data->statusText = lng_chat_status_members(lt_count, chat->count);
|
||||
} else {
|
||||
data->statusText = lang(lng_group_status);
|
||||
}
|
||||
} else if (peer->isMegagroup()) {
|
||||
data->statusText = lang(lng_group_status);
|
||||
|
|
|
@ -86,7 +86,11 @@ void LanguageBox::Inner::languageChanged(int languageIndex) {
|
|||
|
||||
activateCurrent();
|
||||
auto languageId = (*_languages)[languageIndex].id;
|
||||
Lang::CurrentCloudManager().switchToLanguage(languageId);
|
||||
if (Lang::Current().id() != languageId) {
|
||||
// "custom" is applied each time it is passed to switchToLanguage().
|
||||
// So we check that the language really has changed.
|
||||
Lang::CurrentCloudManager().switchToLanguage(languageId);
|
||||
}
|
||||
}
|
||||
|
||||
void LanguageBox::Inner::activateCurrent() {
|
||||
|
|
|
@ -713,9 +713,11 @@ void StickersBox::Inner::paintRow(Painter &p, int index, TimeMs ms) {
|
|||
}
|
||||
}
|
||||
|
||||
auto statusText = (s->count > 0) ? lng_stickers_count(lt_count, s->count) : lang(lng_contacts_loading);
|
||||
|
||||
p.setFont(st::contactsStatusFont);
|
||||
p.setPen(st::contactsStatusFg);
|
||||
p.drawTextLeft(statusx, statusy, width(), lng_stickers_count(lt_count, s->count));
|
||||
p.drawTextLeft(statusx, statusy, width(), statusText);
|
||||
|
||||
p.setOpacity(1);
|
||||
if (xadd || yadd) p.translate(-xadd, -yadd);
|
||||
|
|
|
@ -676,9 +676,10 @@ void StickersListWidget::paintFeaturedStickers(Painter &p, QRect clip) {
|
|||
}
|
||||
}
|
||||
|
||||
auto statusText = (size > 0) ? lng_stickers_count(lt_count, size) : lang(lng_contacts_loading);
|
||||
p.setFont(st::stickersTrendingSubheaderFont);
|
||||
p.setPen(st::stickersTrendingSubheaderFg);
|
||||
p.drawTextLeft(st::emojiPanHeaderLeft - st::buttonRadius, y + st::stickersTrendingSubheaderTop, width(), lng_stickers_count(lt_count, size));
|
||||
p.drawTextLeft(st::emojiPanHeaderLeft - st::buttonRadius, y + st::stickersTrendingSubheaderTop, width(), statusText);
|
||||
|
||||
y += st::stickersTrendingHeader;
|
||||
if (y >= clip.y() + clip.height()) break;
|
||||
|
|
|
@ -31,8 +31,6 @@ namespace codegen {
|
|||
namespace lang {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxPluralVariants = 6;
|
||||
|
||||
char hexChar(uchar ch) {
|
||||
if (ch < 10) {
|
||||
return '0' + ch;
|
||||
|
@ -120,7 +118,6 @@ bool Generator::writeHeader() {
|
|||
header_->include("lang/lang_tag.h").newline().pushNamespace("Lang").stream() << "\
|
||||
\n\
|
||||
constexpr auto kTagsCount = " << langpack_.tags.size() << ";\n\
|
||||
constexpr auto kTagsPluralVariants = " << kMaxPluralVariants << ";\n\
|
||||
\n";
|
||||
|
||||
header_->popNamespace().newline();
|
||||
|
@ -141,21 +138,26 @@ enum LangKey {\n";
|
|||
\n\
|
||||
QString lang(LangKey key);\n\
|
||||
\n";
|
||||
|
||||
for (auto &entry : langpack_.entries) {
|
||||
if (!entry.tags.empty()) {
|
||||
auto &key = entry.key;
|
||||
auto params = QStringList();
|
||||
auto applyTags = QStringList();
|
||||
for (auto &tagData : entry.tags) {
|
||||
auto &tag = tagData.tag;
|
||||
auto isPlural = isTagPlural(key, tag);
|
||||
params.push_back("lngtag_" + tag + ", " + (isPlural ? "float64 " : "const QString &") + tag + "__val");
|
||||
applyTags.push_back("\tresult = Lang::Tag(result, lt_" + tag + ", " + (isPlural ? ("Lang::Plural(" + key + "__" + tag + "0, lt_" + tag + ", " + tag + "__val)") : (tag + "__val")) + ");");
|
||||
auto isPlural = !entry.keyBase.isEmpty();
|
||||
auto &key = entry.key;
|
||||
auto params = QStringList();
|
||||
auto applyTags = QStringList();
|
||||
for (auto &tagData : entry.tags) {
|
||||
auto &tag = tagData.tag;
|
||||
auto isPluralTag = isPlural && (tag == kPluralTag);
|
||||
params.push_back("lngtag_" + tag + ", " + (isPluralTag ? "float64 " : "const QString &") + tag + "__val");
|
||||
if (!isPluralTag) {
|
||||
applyTags.push_back("\tresult = Lang::Tag(result, lt_" + tag + ", " + tag + "__val);\n");
|
||||
}
|
||||
}
|
||||
if (!entry.tags.empty() && (!isPlural || key == ComputePluralKey(entry.keyBase, 0))) {
|
||||
auto initialString = isPlural ? ("Lang::Plural(" + key + ", lt_" + kPluralTag + ", " + kPluralTag + "__val)") : ("lang(" + getFullKey(entry) + ")");
|
||||
header_->stream() << "\
|
||||
inline QString " << entry.key << "(" << params.join(QString(", ")) << ") {\n\
|
||||
auto result = lang(" << entry.key << "__tagged);\n\
|
||||
" << applyTags.join('\n') << ";\n\
|
||||
inline QString " << (isPlural ? entry.keyBase : key) << "(" << params.join(QString(", ")) << ") {\n\
|
||||
auto result = " << initialString << ";\n\
|
||||
" << applyTags.join(QString()) << "\
|
||||
return result;\n\
|
||||
}\n\
|
||||
\n";
|
||||
|
@ -167,7 +169,6 @@ inline QString " << entry.key << "(" << params.join(QString(", ")) << ") {\n\
|
|||
const char *GetKeyName(LangKey key);\n\
|
||||
ushort GetTagIndex(QLatin1String tag);\n\
|
||||
LangKey GetKeyIndex(QLatin1String key);\n\
|
||||
LangKey GetSubkeyIndex(LangKey key, ushort tag, ushort index);\n\
|
||||
bool IsTagReplaced(LangKey key, ushort tag);\n\
|
||||
QString GetOriginalValue(LangKey key);\n\
|
||||
\n";
|
||||
|
@ -258,15 +259,19 @@ LangKey GetKeyIndex(QLatin1String key) {\n\
|
|||
auto taggedKeys = std::map<QString, QString>();
|
||||
auto keysSet = std::set<QString, std::greater<QString>>();
|
||||
for (auto &entry : langpack_.entries) {
|
||||
if (entry.key.mid(0, entry.key.size() - 1).endsWith("__count")) {
|
||||
continue;
|
||||
if (!entry.keyBase.isEmpty()) {
|
||||
for (auto i = 0; i != kPluralPartCount; ++i) {
|
||||
auto keyName = entry.keyBase + '#' + kPluralParts[i];
|
||||
taggedKeys.emplace(keyName, ComputePluralKey(entry.keyBase, i));
|
||||
keysSet.insert(keyName);
|
||||
}
|
||||
} else {
|
||||
auto full = getFullKey(entry);
|
||||
if (full != entry.key) {
|
||||
taggedKeys.emplace(entry.key, full);
|
||||
}
|
||||
keysSet.insert(entry.key);
|
||||
}
|
||||
|
||||
auto full = getFullKey(entry);
|
||||
if (full != entry.key) {
|
||||
taggedKeys.emplace(entry.key, full);
|
||||
}
|
||||
keysSet.insert(entry.key);
|
||||
}
|
||||
|
||||
writeSetSearch(keysSet, [&taggedKeys](const QString &key) {
|
||||
|
@ -277,44 +282,28 @@ LangKey GetKeyIndex(QLatin1String key) {\n\
|
|||
source_->stream() << "\
|
||||
}\n\
|
||||
\n\
|
||||
LangKey GetSubkeyIndex(LangKey key, ushort tag, ushort index) {\n\
|
||||
if (index >= kTagsPluralVariants) return kLangKeysCount;\n\
|
||||
\n\
|
||||
switch (key) {\n";
|
||||
|
||||
for (auto &entry : langpack_.entries) {
|
||||
auto cases = QString();
|
||||
for (auto &tag : entry.tags) {
|
||||
if (isTagPlural(entry.key, tag.tag)) {
|
||||
cases += "\t\t\tcase lt_" + tag.tag + ": return LangKey(" + entry.key + "__" + tag.tag + "0 + index);\n";
|
||||
}
|
||||
}
|
||||
if (cases.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
source_->stream() << "\
|
||||
case " << entry.key << "__tagged: {\n\
|
||||
switch (tag) {\n\
|
||||
" << cases << "\
|
||||
}\n\
|
||||
} break;\n";
|
||||
}
|
||||
|
||||
source_->stream() << "\
|
||||
}\n\
|
||||
\n\
|
||||
return kLangKeysCount;\n\
|
||||
}\n\
|
||||
\n\
|
||||
bool IsTagReplaced(LangKey key, ushort tag) {\n\
|
||||
switch (key) {\n";
|
||||
|
||||
auto lastWrittenPluralEntry = QString();
|
||||
for (auto &entry : langpack_.entries) {
|
||||
if (entry.tags.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (!entry.keyBase.isEmpty()) {
|
||||
if (entry.keyBase == lastWrittenPluralEntry) {
|
||||
continue;
|
||||
}
|
||||
lastWrittenPluralEntry = entry.keyBase;
|
||||
for (auto i = 0; i != kPluralPartCount; ++i) {
|
||||
source_->stream() << "\
|
||||
case " << ComputePluralKey(entry.keyBase, i) << ":" << ((i + 1 == kPluralPartCount) ? " {" : "") << "\n";
|
||||
}
|
||||
} else {
|
||||
source_->stream() << "\
|
||||
case " << getFullKey(entry) << ": {\n";
|
||||
}
|
||||
source_->stream() << "\
|
||||
case " << entry.key << "__tagged: {\n\
|
||||
switch (tag) {\n";
|
||||
for (auto &tag : entry.tags) {
|
||||
source_->stream() << "\
|
||||
|
@ -480,21 +469,11 @@ void Generator::writeSetSearch(const std::set<QString, std::greater<QString>> &s
|
|||
}
|
||||
|
||||
QString Generator::getFullKey(const LangPack::Entry &entry) {
|
||||
if (entry.tags.empty()) {
|
||||
if (!entry.keyBase.isEmpty() || entry.tags.empty()) {
|
||||
return entry.key;
|
||||
}
|
||||
return entry.key + "__tagged";
|
||||
}
|
||||
|
||||
bool Generator::isTagPlural(const QString &key, const QString &tag) const {
|
||||
auto searchForKey = key + "__" + tag + "0";
|
||||
for (auto &entry : langpack_.entries) {
|
||||
if (entry.key == searchForKey) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace lang
|
||||
} // namespace codegen
|
||||
|
|
|
@ -43,7 +43,6 @@ public:
|
|||
|
||||
private:
|
||||
QString getFullKey(const LangPack::Entry &entry);
|
||||
bool isTagPlural(const QString &key, const QString &tag) const;
|
||||
|
||||
template <typename ComputeResult>
|
||||
void writeSetSearch(const std::set<QString, std::greater<QString>> &set, ComputeResult computeResult, const QString &invalidResult);
|
||||
|
|
|
@ -46,7 +46,7 @@ bool ValidateAnsiString(const QString &value) {
|
|||
}
|
||||
|
||||
bool ValidateKey(const QString &key) {
|
||||
static const auto validator = QRegularExpression("^[a-z0-9_.-]+$", QRegularExpression::CaseInsensitiveOption);
|
||||
static const auto validator = QRegularExpression("^[a-z0-9_.-]+(#(one|other))?$", QRegularExpression::CaseInsensitiveOption);
|
||||
if (!validator.match(key).hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -78,6 +78,21 @@ QString PrepareCommandString(int index) {
|
|||
|
||||
} // namespace
|
||||
|
||||
const std::array<QString, kPluralPartCount> kPluralParts = {
|
||||
"zero",
|
||||
"one",
|
||||
"two",
|
||||
"few",
|
||||
"many",
|
||||
"other",
|
||||
};
|
||||
|
||||
const QString kPluralTag = "count";
|
||||
|
||||
QString ComputePluralKey(const QString &base, int index) {
|
||||
return base + "__plural" + QString::number(index);
|
||||
}
|
||||
|
||||
ParsedFile::ParsedFile(const Options &options)
|
||||
: filePath_(options.inputPath)
|
||||
, file_(filePath_)
|
||||
|
@ -104,7 +119,7 @@ bool ParsedFile::read() {
|
|||
logErrorUnexpectedToken() << "'=' for '" << keyToken.value.toStdString() << "' key";
|
||||
}
|
||||
} else {
|
||||
logErrorUnexpectedToken() << "string key name (/^[a-z0-9_.-]+$/i)";
|
||||
logErrorUnexpectedToken() << "string key name (/^[a-z0-9_.-]+(#(one|other))?$/i)";
|
||||
}
|
||||
}
|
||||
if (file_.atEnd()) {
|
||||
|
@ -113,9 +128,46 @@ bool ParsedFile::read() {
|
|||
logErrorUnexpectedToken() << "ansi string key name";
|
||||
} while (!failed());
|
||||
|
||||
fillPluralTags();
|
||||
|
||||
return !failed();
|
||||
}
|
||||
|
||||
void ParsedFile::fillPluralTags() {
|
||||
auto count = result_.entries.size();
|
||||
for (auto i = 0; i != count;) {
|
||||
auto &baseEntry = result_.entries[i];
|
||||
if (baseEntry.keyBase.isEmpty()) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
logAssert(i + kPluralPartCount < count);
|
||||
|
||||
// Accumulate all tags from all plural variants.
|
||||
auto tags = std::vector<LangPack::Tag>();
|
||||
for (auto j = i; j != i + kPluralPartCount; ++j) {
|
||||
if (tags.empty()) {
|
||||
tags = result_.entries[j].tags;
|
||||
} else {
|
||||
for (auto &tag : result_.entries[j].tags) {
|
||||
if (std::find(tags.begin(), tags.end(), tag) == tags.end()) {
|
||||
tags.push_back(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logAssert(!tags.empty());
|
||||
logAssert(tags.front().tag == kPluralTag);
|
||||
|
||||
// Set this tags list to all plural variants.
|
||||
for (auto j = i; j != i + kPluralPartCount; ++j) {
|
||||
result_.entries[j].tags = tags;
|
||||
}
|
||||
|
||||
i += kPluralPartCount;
|
||||
}
|
||||
}
|
||||
|
||||
BasicToken ParsedFile::assertNextToken(BasicToken::Type type) {
|
||||
auto result = file_.getToken(type);
|
||||
if (!result) {
|
||||
|
@ -204,24 +256,85 @@ QString ParsedFile::extractTagData(const QString &tagText, LangPack *to) {
|
|||
return PrepareCommandString(tagIndex);
|
||||
}
|
||||
|
||||
void ParsedFile::addEntity(const QString &key, const QString &value) {
|
||||
for (auto &entry : result_.entries) {
|
||||
if (entry.key == key) {
|
||||
logError(kErrorBadString) << "duplicate found for key '" << key.toStdString() << "'";
|
||||
void ParsedFile::addEntity(QString key, const QString &value) {
|
||||
auto pluralPartOffset = key.indexOf('#');
|
||||
auto pluralIndex = -1;
|
||||
if (pluralPartOffset >= 0) {
|
||||
auto pluralPart = key.mid(pluralPartOffset + 1);
|
||||
pluralIndex = std::find(kPluralParts.begin(), kPluralParts.end(), pluralPart) - kPluralParts.begin();
|
||||
if (pluralIndex < 0 || pluralIndex >= kPluralParts.size()) {
|
||||
logError(kErrorBadString) << "bad plural part for key '" << key.toStdString() << "': '" << pluralPart.toStdString() << "'";
|
||||
return;
|
||||
}
|
||||
key = key.mid(0, pluralPartOffset);
|
||||
}
|
||||
auto checkKey = [this](const QString &key) {
|
||||
for (auto &entry : result_.entries) {
|
||||
if (entry.key == key) {
|
||||
if (entry.keyBase.isEmpty() || !entry.tags.empty()) {
|
||||
// Empty tags in plural entry means it was not encountered yet.
|
||||
logError(kErrorBadString) << "duplicate found for key '" << key.toStdString() << "'";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if (!checkKey(key)) {
|
||||
return;
|
||||
}
|
||||
auto tagsData = LangPack();
|
||||
auto entry = LangPack::Entry();
|
||||
entry.key = key;
|
||||
entry.value = extractTagsData(value, &tagsData);
|
||||
entry.tags = tagsData.tags;
|
||||
result_.entries.push_back(entry);
|
||||
for (auto &pluralEntry : tagsData.entries) {
|
||||
auto taggedEntry = LangPack::Entry();
|
||||
taggedEntry.key = key + "__" + pluralEntry.key;
|
||||
taggedEntry.value = pluralEntry.value;
|
||||
result_.entries.push_back(taggedEntry);
|
||||
if (pluralIndex >= 0) {
|
||||
logAssert(tagsData.entries.empty());
|
||||
|
||||
entry.keyBase = entry.key;
|
||||
entry.key = ComputePluralKey(entry.keyBase, pluralIndex);
|
||||
if (!checkKey(entry.key)) {
|
||||
return;
|
||||
}
|
||||
auto baseIndex = -1;
|
||||
auto alreadyCount = result_.entries.size();
|
||||
for (auto i = 0; i != alreadyCount; ++i) {
|
||||
if (result_.entries[i].keyBase == entry.keyBase) {
|
||||
// This is not the first appearance of this plural key.
|
||||
baseIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (baseIndex < 0) {
|
||||
baseIndex = result_.entries.size();
|
||||
for (auto i = 0; i != kPluralPartCount; ++i) {
|
||||
auto addingEntry = LangPack::Entry();
|
||||
addingEntry.keyBase = entry.keyBase;
|
||||
addingEntry.key = ComputePluralKey(entry.keyBase, i);
|
||||
result_.entries.push_back(addingEntry);
|
||||
}
|
||||
}
|
||||
auto entryIndex = baseIndex + pluralIndex;
|
||||
logAssert(entryIndex < result_.entries.size());
|
||||
auto &realEntry = result_.entries[entryIndex];
|
||||
logAssert(realEntry.key == entry.key);
|
||||
realEntry.value = entry.value;
|
||||
|
||||
// Add all new tags to the existing ones.
|
||||
realEntry.tags = std::vector<LangPack::Tag>(1, LangPack::Tag { kPluralTag });
|
||||
for (auto &tag : entry.tags) {
|
||||
if (std::find(realEntry.tags.begin(), realEntry.tags.end(), tag) == realEntry.tags.end()) {
|
||||
realEntry.tags.push_back(tag);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result_.entries.push_back(entry);
|
||||
for (auto &pluralEntry : tagsData.entries) {
|
||||
auto taggedEntry = LangPack::Entry();
|
||||
taggedEntry.key = key + "__" + pluralEntry.key;
|
||||
taggedEntry.value = pluralEntry.value;
|
||||
result_.entries.push_back(taggedEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <QImage>
|
||||
#include "codegen/common/basic_tokenized_file.h"
|
||||
|
@ -30,6 +31,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
namespace codegen {
|
||||
namespace lang {
|
||||
|
||||
constexpr auto kPluralPartCount = 6;
|
||||
extern const std::array<QString, kPluralPartCount> kPluralParts;
|
||||
extern const QString kPluralTag;
|
||||
QString ComputePluralKey(const QString &base, int index);
|
||||
|
||||
struct LangPack {
|
||||
struct Tag {
|
||||
QString tag;
|
||||
|
@ -37,6 +43,7 @@ struct LangPack {
|
|||
struct Entry {
|
||||
QString key;
|
||||
QString value;
|
||||
QString keyBase; // Empty for not plural entries.
|
||||
std::vector<Tag> tags;
|
||||
};
|
||||
std::vector<Entry> entries;
|
||||
|
@ -44,6 +51,14 @@ struct LangPack {
|
|||
|
||||
};
|
||||
|
||||
inline bool operator==(const LangPack::Tag &a, const LangPack::Tag &b) {
|
||||
return a.tag == b.tag;
|
||||
}
|
||||
|
||||
inline bool operator!=(const LangPack::Tag &a, const LangPack::Tag &b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
// Parses an input file to the internal struct.
|
||||
class ParsedFile {
|
||||
public:
|
||||
|
@ -83,10 +98,12 @@ private:
|
|||
using BasicToken = common::BasicTokenizedFile::Token;
|
||||
BasicToken assertNextToken(BasicToken::Type type);
|
||||
|
||||
void addEntity(const QString &key, const QString &value);
|
||||
void addEntity(QString key, const QString &value);
|
||||
QString extractTagsData(const QString &value, LangPack *to);
|
||||
QString extractTagData(const QString &tag, LangPack *to);
|
||||
|
||||
void fillPluralTags();
|
||||
|
||||
QString filePath_;
|
||||
common::BasicTokenizedFile file_;
|
||||
Options options_;
|
||||
|
|
|
@ -326,7 +326,7 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO
|
|||
}
|
||||
|
||||
if (_state == SearchedState || !_searchResults.empty()) {
|
||||
QString text = lng_search_found_results(lt_count, _searchResults.empty() ? 0 : (_searchedMigratedCount + _searchedCount));
|
||||
auto text = _searchResults.empty() ? lang(lng_search_no_results) : lng_search_found_results(lt_count, _searchedMigratedCount + _searchedCount);
|
||||
p.fillRect(0, 0, fullWidth, st::searchedBarHeight, st::searchedBarBg);
|
||||
if (!paintingOther) {
|
||||
p.setFont(st::searchedBarFont);
|
||||
|
|
|
@ -2158,9 +2158,9 @@ HistoryService::PreparedText HistoryService::prepareGameScoreText() {
|
|||
result.links.push_back(fromLink());
|
||||
auto gameTitle = computeGameTitle();
|
||||
if (gameTitle.isEmpty()) {
|
||||
result.text = lng_action_game_score_no_game(lt_from, fromLinkText(), lt_count, scoreNumber);
|
||||
result.text = lng_action_game_score_no_game(lt_count, scoreNumber, lt_from, fromLinkText());
|
||||
} else {
|
||||
result.text = lng_action_game_score(lt_from, fromLinkText(), lt_count, scoreNumber, lt_game, gameTitle);
|
||||
result.text = lng_action_game_score(lt_count, scoreNumber, lt_from, fromLinkText(), lt_game, gameTitle);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -4031,24 +4031,34 @@ void HistoryWidget::updateOnlineDisplay() {
|
|||
text = App::onlineText(user, t);
|
||||
titlePeerTextOnline = App::onlineColorUse(user, t);
|
||||
} else if (_peer->isChat()) {
|
||||
ChatData *chat = _peer->asChat();
|
||||
auto chat = _peer->asChat();
|
||||
if (!chat->amIn()) {
|
||||
text = lang(lng_chat_status_unaccessible);
|
||||
} else if (chat->participants.isEmpty()) {
|
||||
text = _titlePeerText.isEmpty() ? lng_chat_status_members(lt_count, chat->count < 0 ? 0 : chat->count) : _titlePeerText;
|
||||
if (!_titlePeerText.isEmpty()) {
|
||||
text = _titlePeerText;
|
||||
} else if (chat->count <= 0) {
|
||||
text = lang(lng_group_status);
|
||||
} else {
|
||||
text = lng_chat_status_members(lt_count, chat->count);
|
||||
}
|
||||
} else {
|
||||
int32 onlineCount = 0;
|
||||
bool onlyMe = true;
|
||||
for (ChatData::Participants::const_iterator i = chat->participants.cbegin(), e = chat->participants.cend(); i != e; ++i) {
|
||||
auto online = 0;
|
||||
auto onlyMe = true;
|
||||
for (auto i = chat->participants.cbegin(), e = chat->participants.cend(); i != e; ++i) {
|
||||
if (i.key()->onlineTill > t) {
|
||||
++onlineCount;
|
||||
++online;
|
||||
if (onlyMe && i.key() != App::self()) onlyMe = false;
|
||||
}
|
||||
}
|
||||
if (onlineCount && !onlyMe) {
|
||||
text = lng_chat_status_members_online(lt_count, chat->participants.size(), lt_count_online, onlineCount);
|
||||
} else {
|
||||
if (online > 0 && !onlyMe) {
|
||||
auto membersCount = lng_chat_status_members(lt_count, chat->participants.size());
|
||||
auto onlineCount = lng_chat_status_online(lt_count, online);
|
||||
text = lng_chat_status_members_online(lt_members_count, membersCount, lt_online_count, onlineCount);
|
||||
} else if (chat->participants.size() > 0) {
|
||||
text = lng_chat_status_members(lt_count, chat->participants.size());
|
||||
} else {
|
||||
text = lang(lng_group_status);
|
||||
}
|
||||
}
|
||||
} else if (_peer->isChannel()) {
|
||||
|
@ -4056,21 +4066,27 @@ void HistoryWidget::updateOnlineDisplay() {
|
|||
if (_peer->asChannel()->mgInfo->lastParticipants.size() < _peer->asChannel()->membersCount() || _peer->asChannel()->lastParticipantsCountOutdated()) {
|
||||
if (App::api()) App::api()->requestLastParticipants(_peer->asChannel());
|
||||
}
|
||||
int32 onlineCount = 0;
|
||||
auto online = 0;
|
||||
bool onlyMe = true;
|
||||
for (auto i = _peer->asChannel()->mgInfo->lastParticipants.cbegin(), e = _peer->asChannel()->mgInfo->lastParticipants.cend(); i != e; ++i) {
|
||||
if ((*i)->onlineTill > t) {
|
||||
++onlineCount;
|
||||
++online;
|
||||
if (onlyMe && (*i) != App::self()) onlyMe = false;
|
||||
}
|
||||
}
|
||||
if (onlineCount && !onlyMe) {
|
||||
text = lng_chat_status_members_online(lt_count, _peer->asChannel()->membersCount(), lt_count_online, onlineCount);
|
||||
} else {
|
||||
if (online && !onlyMe) {
|
||||
auto membersCount = lng_chat_status_members(lt_count, _peer->asChannel()->membersCount());
|
||||
auto onlineCount = lng_chat_status_online(lt_count, online);
|
||||
text = lng_chat_status_members_online(lt_members_count, membersCount, lt_online_count, onlineCount);
|
||||
} else if (_peer->asChannel()->membersCount() > 0) {
|
||||
text = lng_chat_status_members(lt_count, _peer->asChannel()->membersCount());
|
||||
} else {
|
||||
text = lang(lng_group_status);
|
||||
}
|
||||
} else if (_peer->asChannel()->membersCount() > 0) {
|
||||
text = lng_chat_status_members(lt_count, _peer->asChannel()->membersCount());
|
||||
} else {
|
||||
text = _peer->asChannel()->membersCount() ? lng_chat_status_members(lt_count, _peer->asChannel()->membersCount()) : lang(_peer->isMegagroup() ? lng_group_status : lng_channel_status);
|
||||
text = lang(_peer->isMegagroup() ? lng_group_status : lng_channel_status);
|
||||
}
|
||||
}
|
||||
if (_titlePeerText != text) {
|
||||
|
|
|
@ -256,17 +256,18 @@ void Widget::resetAccount() {
|
|||
|
||||
auto type = error.type();
|
||||
if (type.startsWith(qstr("2FA_CONFIRM_WAIT_"))) {
|
||||
int seconds = type.mid(qstr("2FA_CONFIRM_WAIT_").size()).toInt();
|
||||
int days = (seconds + 59) / 86400;
|
||||
int hours = ((seconds + 59) % 86400) / 3600;
|
||||
int minutes = ((seconds + 59) % 3600) / 60;
|
||||
QString when;
|
||||
auto seconds = type.mid(qstr("2FA_CONFIRM_WAIT_").size()).toInt();
|
||||
auto days = (seconds + 59) / 86400;
|
||||
auto hours = ((seconds + 59) % 86400) / 3600;
|
||||
auto minutes = ((seconds + 59) % 3600) / 60;
|
||||
auto when = lng_signin_reset_minutes(lt_count, minutes);
|
||||
if (days > 0) {
|
||||
when = lng_signin_reset_in_days(lt_count_days, days, lt_count_hours, hours, lt_count_minutes, minutes);
|
||||
auto daysCount = lng_signin_reset_days(lt_count, days);
|
||||
auto hoursCount = lng_signin_reset_hours(lt_count, hours);
|
||||
when = lng_signin_reset_in_days(lt_days_count, daysCount, lt_hours_count, hoursCount, lt_minutes_count, when);
|
||||
} else if (hours > 0) {
|
||||
when = lng_signin_reset_in_hours(lt_count_hours, hours, lt_count_minutes, minutes);
|
||||
} else {
|
||||
when = lng_signin_reset_in_minutes(lt_count_minutes, minutes);
|
||||
auto hoursCount = lng_signin_reset_hours(lt_count, hours);
|
||||
when = lng_signin_reset_in_hours(lt_hours_count, hoursCount, lt_minutes_count, when);
|
||||
}
|
||||
Ui::show(Box<InformBox>(lng_signin_reset_wait(lt_phone_number, App::formatPhone(getData()->phone), lt_when, when)));
|
||||
} else if (type == qstr("2FA_RECENT_CONFIRM")) {
|
||||
|
|
|
@ -196,7 +196,7 @@ void CloudManager::switchToLanguage(QString id) {
|
|||
if (id.isEmpty()) {
|
||||
id = DefaultLanguageId();
|
||||
}
|
||||
if (_langpack.id() == id) {
|
||||
if (_langpack.id() == id && id != qstr("custom")) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ bool FileParser::readKeyValue(const char *&from, const char *end) {
|
|||
}
|
||||
++from;
|
||||
const char *nameStart = from;
|
||||
while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9'))) {
|
||||
while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9') || *from == '#')) {
|
||||
++from;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
#include "platform/platform_specific.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "lang/lang_file_parser.h"
|
||||
#include "base/qthelp_regex.h"
|
||||
|
||||
namespace Lang {
|
||||
namespace {
|
||||
|
@ -43,6 +44,10 @@ constexpr str_const kLegacyLanguages[] = {
|
|||
"ko",
|
||||
};
|
||||
|
||||
QString ConvertLegacyLanguageId(const QString &languageId) {
|
||||
return languageId.toLower().replace('_', '-');
|
||||
}
|
||||
|
||||
class ValueParser {
|
||||
public:
|
||||
ValueParser(const QByteArray &key, LangKey keyIndex, const QByteArray &value);
|
||||
|
@ -51,17 +56,11 @@ public:
|
|||
Expects(!_failed);
|
||||
return std::move(_result);
|
||||
}
|
||||
std::map<LangKey, QString> takePluralValues() {
|
||||
Expects(!_failed);
|
||||
return std::move(_plural);
|
||||
}
|
||||
|
||||
bool parse();
|
||||
|
||||
private:
|
||||
void appendToResult(const char *nextBegin);
|
||||
void appendToPlural(const char *nextBegin);
|
||||
bool feedPluralValue();
|
||||
bool logError(const QString &text);
|
||||
bool readTag();
|
||||
|
||||
|
@ -72,16 +71,11 @@ private:
|
|||
ushort _currentTagIndex = 0;
|
||||
QString _currentTagReplacer;
|
||||
|
||||
QString _pluralValue;
|
||||
int _pluralIndex = 0;
|
||||
bool _pluralNumericFound = false;
|
||||
|
||||
bool _failed = true;
|
||||
|
||||
const char *_begin, *_ch, *_end;
|
||||
|
||||
QString _result;
|
||||
std::map<LangKey, QString> _plural;
|
||||
OrderedSet<ushort> _tagsUsed;
|
||||
|
||||
};
|
||||
|
@ -99,29 +93,6 @@ void ValueParser::appendToResult(const char *nextBegin) {
|
|||
_begin = nextBegin;
|
||||
}
|
||||
|
||||
void ValueParser::appendToPlural(const char *nextBegin) {
|
||||
if (_ch > _begin) _pluralValue.append(QString::fromUtf8(_begin, _ch - _begin));
|
||||
_begin = nextBegin;
|
||||
}
|
||||
|
||||
bool ValueParser::feedPluralValue() {
|
||||
appendToPlural(_ch + 1);
|
||||
|
||||
if (_pluralIndex >= kTagsPluralVariants) {
|
||||
return logError("Too many values inside counted tag");
|
||||
}
|
||||
auto pluralKeyIndex = GetSubkeyIndex(_keyIndex, _currentTagIndex, _pluralIndex);
|
||||
if (pluralKeyIndex == kLangKeysCount) {
|
||||
return logError("Unexpected counted tag");
|
||||
} else {
|
||||
_plural.emplace(pluralKeyIndex, _pluralValue);
|
||||
}
|
||||
++_pluralIndex;
|
||||
_pluralValue = QString();
|
||||
_pluralNumericFound = false;
|
||||
return true;
|
||||
};
|
||||
|
||||
bool ValueParser::logError(const QString &text) {
|
||||
_failed = true;
|
||||
auto loggedKey = (_currentTag.size() > 0) ? (_key + QString(':') + _currentTag) : QString(_key);
|
||||
|
@ -149,8 +120,8 @@ bool ValueParser::readTag() {
|
|||
}
|
||||
|
||||
_currentTag = QLatin1String(tagStart, _ch - tagStart);
|
||||
if (_ch == _end || (*_ch != '}' && *_ch != ':')) {
|
||||
return logError("Expected '}' or ':' after tag name");
|
||||
if (_ch == _end || *_ch != '}') {
|
||||
return logError("Expected '}' after tag name");
|
||||
}
|
||||
|
||||
_currentTagIndex = GetTagIndex(_currentTag);
|
||||
|
@ -189,39 +160,6 @@ bool ValueParser::parse() {
|
|||
_result.append(_currentTagReplacer);
|
||||
|
||||
_begin = _ch + 1;
|
||||
if (*_ch == ':') {
|
||||
_pluralIndex = 0;
|
||||
while (_ch != _end && *_ch != '}') {
|
||||
if (*_ch == '|') {
|
||||
if (!feedPluralValue()) {
|
||||
return false;
|
||||
}
|
||||
} else if (*_ch == '\\') {
|
||||
if (_ch + 1 >= _end) {
|
||||
return logError("Unexpected end of file inside counted tag");
|
||||
}
|
||||
if (*(_ch + 1) == '{' || *(_ch + 1) == '#' || *(_ch + 1) == '}') {
|
||||
appendToPlural(_ch + 1);
|
||||
}
|
||||
} else if (*_ch == '{') {
|
||||
return logError("Unexpected tag inside counted tag");
|
||||
} else if (*_ch == '#') {
|
||||
if (_pluralNumericFound) {
|
||||
return logError("Replacement '#' double used inside counted tag");
|
||||
}
|
||||
_pluralNumericFound = true;
|
||||
appendToPlural(_ch + 1);
|
||||
_pluralValue.append(_currentTagReplacer);
|
||||
}
|
||||
++_ch;
|
||||
}
|
||||
if (_ch == _end) {
|
||||
return logError("Unexpected end of value inside counted tag");
|
||||
}
|
||||
if (!feedPluralValue()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_currentTag = QLatin1String();
|
||||
}
|
||||
}
|
||||
|
@ -259,6 +197,7 @@ void Instance::switchToId(const QString &id) {
|
|||
}
|
||||
_updated.notify();
|
||||
}
|
||||
updatePluralRules();
|
||||
}
|
||||
|
||||
void Instance::switchToCustomFile(const QString &filePath) {
|
||||
|
@ -271,6 +210,7 @@ void Instance::switchToCustomFile(const QString &filePath) {
|
|||
void Instance::reset() {
|
||||
_values.clear();
|
||||
_nonDefaultValues.clear();
|
||||
_nonDefaultSet.clear();
|
||||
_legacyId = kLegacyLanguageNone;
|
||||
_customFilePathAbsolute = QString();
|
||||
_customFilePathRelative = QString();
|
||||
|
@ -286,6 +226,7 @@ void Instance::fillDefaults() {
|
|||
for (auto i = 0; i != kLangKeysCount; ++i) {
|
||||
_values.emplace_back(GetOriginalValue(LangKey(i)));
|
||||
}
|
||||
_nonDefaultSet = std::vector<uchar>(kLangKeysCount, 0);
|
||||
}
|
||||
|
||||
QString Instance::systemLangCode() const {
|
||||
|
@ -388,6 +329,7 @@ void Instance::fillFromSerialized(const QByteArray &data) {
|
|||
for (auto i = 0, count = nonDefaultValuesCount * 2; i != count; i += 2) {
|
||||
applyValue(nonDefaultStrings[i], nonDefaultStrings[i + 1]);
|
||||
}
|
||||
updatePluralRules();
|
||||
}
|
||||
|
||||
void Instance::loadFromContent(const QByteArray &content) {
|
||||
|
@ -416,6 +358,7 @@ void Instance::fillFromCustomFile(const QString &filePath) {
|
|||
auto content = Lang::FileParser::ReadFile(absolutePath, relativePath);
|
||||
if (!content.isEmpty()) {
|
||||
loadFromCustomContent(absolutePath, relativePath, content);
|
||||
updatePluralRules();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -441,6 +384,8 @@ void Instance::fillFromLegacy(int legacyId, const QString &legacyPath) {
|
|||
loadFromContent(content);
|
||||
}
|
||||
}
|
||||
_id = ConvertLegacyLanguageId(_id);
|
||||
updatePluralRules();
|
||||
}
|
||||
|
||||
template <typename SetCallback, typename ResetCallback>
|
||||
|
@ -508,25 +453,41 @@ std::map<LangKey, QString> Instance::ParseStrings(const MTPVector<MTPLangPackStr
|
|||
}
|
||||
|
||||
template <typename Result>
|
||||
void Instance::ParseKeyValue(const QByteArray &key, const QByteArray &value, Result &result) {
|
||||
LangKey Instance::ParseKeyValue(const QByteArray &key, const QByteArray &value, Result &result) {
|
||||
auto keyIndex = GetKeyIndex(QLatin1String(key));
|
||||
if (keyIndex == kLangKeysCount) {
|
||||
LOG(("Lang Error: Unknown key '%1'").arg(QString::fromLatin1(key)));
|
||||
return;
|
||||
return kLangKeysCount;
|
||||
}
|
||||
|
||||
ValueParser parser(key, keyIndex, value);
|
||||
if (parser.parse()) {
|
||||
result[keyIndex] = parser.takeResult();
|
||||
for (auto &plural : parser.takePluralValues()) {
|
||||
result[plural.first] = plural.second;
|
||||
}
|
||||
return keyIndex;
|
||||
}
|
||||
return kLangKeysCount;
|
||||
}
|
||||
|
||||
void Instance::applyValue(const QByteArray &key, const QByteArray &value) {
|
||||
_nonDefaultValues[key] = value;
|
||||
ParseKeyValue(key, value, _values);
|
||||
auto index = ParseKeyValue(key, value, _values);
|
||||
if (index != kLangKeysCount) {
|
||||
_nonDefaultSet[index] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::updatePluralRules() {
|
||||
auto id = _id;
|
||||
if (isCustom()) {
|
||||
auto path = _customFilePathAbsolute.isEmpty() ? _customFilePathRelative : _customFilePathAbsolute;
|
||||
auto name = QFileInfo(path).fileName();
|
||||
if (auto match = qthelp::regex_match("_([a-z]{2,3})(_[A-Z]{2,3}|\\-[a-z]{2,3})?\\.", name)) {
|
||||
id = match->captured(1);
|
||||
}
|
||||
} else if (auto match = qthelp::regex_match("^([a-z]{2,3})(_[A-Z]{2,3}|\\-[a-z]{2,3})$", id)) {
|
||||
id = match->captured(1);
|
||||
}
|
||||
UpdatePluralRules(id);
|
||||
}
|
||||
|
||||
void Instance::resetValue(const QByteArray &key) {
|
||||
|
|
|
@ -75,6 +75,11 @@ public:
|
|||
Expects(_values.size() == kLangKeysCount);
|
||||
return _values[key];
|
||||
}
|
||||
bool isNonDefaultPlural(LangKey key) {
|
||||
Expects(key >= 0 && key < kLangKeysCount);
|
||||
Expects(_nonDefaultSet.size() == kLangKeysCount);
|
||||
return _nonDefaultSet[key] || _nonDefaultSet[key + 1] || _nonDefaultSet[key + 2] || _nonDefaultSet[key + 3] || _nonDefaultSet[key + 4] || _nonDefaultSet[key + 5];
|
||||
}
|
||||
|
||||
private:
|
||||
// SetCallback takes two QByteArrays: key, value.
|
||||
|
@ -85,7 +90,7 @@ private:
|
|||
|
||||
// Writes each key-value pair in the result container.
|
||||
template <typename Result>
|
||||
static void ParseKeyValue(const QByteArray &key, const QByteArray &value, Result &result);
|
||||
static LangKey ParseKeyValue(const QByteArray &key, const QByteArray &value, Result &result);
|
||||
|
||||
void applyValue(const QByteArray &key, const QByteArray &value);
|
||||
void resetValue(const QByteArray &key);
|
||||
|
@ -94,6 +99,7 @@ private:
|
|||
void fillFromCustomFile(const QString &filePath);
|
||||
void loadFromContent(const QByteArray &content);
|
||||
void loadFromCustomContent(const QString &absolutePath, const QString &relativePath, const QByteArray &content);
|
||||
void updatePluralRules();
|
||||
|
||||
QString _id;
|
||||
int _legacyId = kLegacyLanguageNone;
|
||||
|
@ -106,6 +112,7 @@ private:
|
|||
mutable QString _systemLanguage;
|
||||
|
||||
std::vector<QString> _values;
|
||||
std::vector<uchar> _nonDefaultSet;
|
||||
std::map<QByteArray, QByteArray> _nonDefaultValues;
|
||||
|
||||
};
|
||||
|
|
|
@ -23,6 +23,111 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
#include "lang/lang_keys.h"
|
||||
|
||||
namespace Lang {
|
||||
namespace {
|
||||
|
||||
//
|
||||
// http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
|
||||
//
|
||||
|
||||
constexpr auto kShiftZero = ushort(0);
|
||||
constexpr auto kShiftOne = ushort(1);
|
||||
constexpr auto kShiftTwo = ushort(2);
|
||||
constexpr auto kShiftFew = ushort(3);
|
||||
constexpr auto kShiftMany = ushort(4);
|
||||
constexpr auto kShiftOther = ushort(5);
|
||||
|
||||
//
|
||||
// n absolute value of the source number (integer and decimals).
|
||||
// i integer digits of n.
|
||||
// v number of visible fraction digits in n, with trailing zeros.
|
||||
// w number of visible fraction digits in n, without trailing zeros.
|
||||
// f visible fractional digits in n, with trailing zeros.
|
||||
// t visible fractional digits in n, without trailing zeros.
|
||||
//
|
||||
// Let n be int, being -1 for non-integer numbers and n == i for integer numbers.
|
||||
// It is fine while in the rules we compare n only to integers.
|
||||
//
|
||||
// -123.450: n = -1, i = 123, v = 3, w = 2, f = 450, t = 45
|
||||
//
|
||||
|
||||
using ChoosePluralMethod = ushort (*)(int n, int i, int v, int w, int f, int t);
|
||||
|
||||
ushort ChoosePluralAr(int n, int i, int v, int w, int f, int t) {
|
||||
if (n == 0) {
|
||||
return kShiftZero;
|
||||
} else if (n == 1) {
|
||||
return kShiftOne;
|
||||
} else if (n == 2) {
|
||||
return kShiftTwo;
|
||||
} else if (n < 0) {
|
||||
return kShiftOther;
|
||||
}
|
||||
auto mod100 = (n % 100);
|
||||
if (mod100 >= 3 && mod100 <= 10) {
|
||||
return kShiftFew;
|
||||
} else if (mod100 >= 11 && mod100 <= 99) {
|
||||
return kShiftMany;
|
||||
}
|
||||
return kShiftOther;
|
||||
}
|
||||
|
||||
ushort ChoosePluralEn(int n, int i, int v, int w, int f, int t) {
|
||||
if (i == 1 && v == 0) {
|
||||
return kShiftOne;
|
||||
}
|
||||
return kShiftOther;
|
||||
}
|
||||
|
||||
ushort ChoosePluralPt(int n, int i, int v, int w, int f, int t) {
|
||||
if (i == 0 || i == 1) {
|
||||
return kShiftOne;
|
||||
}
|
||||
return kShiftOther;
|
||||
}
|
||||
|
||||
ushort ChoosePluralEs(int n, int i, int v, int w, int f, int t) {
|
||||
if (n == 1) {
|
||||
return kShiftOne;
|
||||
}
|
||||
return kShiftOther;
|
||||
}
|
||||
|
||||
ushort ChoosePluralKo(int n, int i, int v, int w, int f, int t) {
|
||||
return kShiftOther;
|
||||
}
|
||||
|
||||
ushort ChoosePluralRu(int n, int i, int v, int w, int f, int t) {
|
||||
if (v == 0) {
|
||||
auto mod10 = (i % 10);
|
||||
auto mod100 = (i % 100);
|
||||
if ((mod10 == 1) && (mod100 != 11)) {
|
||||
return kShiftOne;
|
||||
} else if ((mod10 >= 2) && (mod10 <= 4) && (mod100 < 12 || mod100 > 14)) {
|
||||
return kShiftFew;
|
||||
} else {
|
||||
return kShiftMany;
|
||||
}
|
||||
}
|
||||
return kShiftMany;// kShiftOther;
|
||||
}
|
||||
|
||||
QMap<QString, ChoosePluralMethod> GeneratePluralRulesMap() {
|
||||
auto result = QMap<QString, ChoosePluralMethod>();
|
||||
result.insert(qsl("ar"), ChoosePluralAr);
|
||||
// result.insert(qsl("de"), ChoosePluralEn);
|
||||
// result.insert(qsl("en"), ChoosePluralEn); // En is default, so we don't fill it inside the map.
|
||||
result.insert(qsl("es"), ChoosePluralEs);
|
||||
// result.insert(qsl("it"), ChoosePluralEn);
|
||||
result.insert(qsl("ko"), ChoosePluralKo);
|
||||
// result.insert(qsl("nl"), ChoosePluralEn);
|
||||
result.insert(qsl("pt"), ChoosePluralPt);
|
||||
result.insert(qsl("ru"), ChoosePluralRu);
|
||||
return result;
|
||||
}
|
||||
|
||||
ChoosePluralMethod ChoosePlural = ChoosePluralEn;
|
||||
|
||||
} // namespace
|
||||
|
||||
QString Tag(const QString &original, ushort tag, const QString &replacement) {
|
||||
for (auto s = original.constData(), ch = s, e = ch + original.size(); ch != e;) {
|
||||
|
@ -53,29 +158,29 @@ QString Tag(const QString &original, ushort tag, const QString &replacement) {
|
|||
return original;
|
||||
}
|
||||
|
||||
QString Plural(ushort key0, ushort tag, float64 value) { // current lang dependent
|
||||
int v = qFloor(value);
|
||||
QString sv;
|
||||
ushort key = key0;
|
||||
if (v != qCeil(value)) {
|
||||
key += 2;
|
||||
sv = QString::number(value);
|
||||
} else {
|
||||
if (v == 1) {
|
||||
key += 1;
|
||||
} else if (v) {
|
||||
key += 2;
|
||||
}
|
||||
sv = QString::number(v);
|
||||
QString Plural(ushort keyBase, ushort tag, float64 value) {
|
||||
// Simplified.
|
||||
auto n = qAbs(value);
|
||||
auto i = qFloor(n);
|
||||
auto integer = (qCeil(n) == i);
|
||||
auto v = integer ? 0 : 6;
|
||||
auto w = v;
|
||||
auto f = integer ? 0 : 111111;
|
||||
auto t = integer ? 0 : 111111;
|
||||
|
||||
auto &langpack = Lang::Current();
|
||||
auto useNonDefaultPlural = (ChoosePlural != ChoosePluralEn && langpack.isNonDefaultPlural(LangKey(keyBase)));
|
||||
auto shift = (useNonDefaultPlural ? ChoosePlural : ChoosePluralEn)((integer ? i : -1), i, v, w, f, t);
|
||||
auto string = langpack.getValue(LangKey(keyBase + shift));
|
||||
if (i == qCeil(n)) {
|
||||
return Tag(string, tag, QString::number(value));
|
||||
}
|
||||
while (key > key0) {
|
||||
auto v = lang(LangKey(key));
|
||||
if (!v.isEmpty()) {
|
||||
return Tag(v, tag, sv);
|
||||
}
|
||||
--key;
|
||||
}
|
||||
return Tag(lang(LangKey(key0)), tag, sv);
|
||||
return Tag(string, tag, QString::number(qRound(value)));
|
||||
}
|
||||
|
||||
void UpdatePluralRules(const QString &languageId) {
|
||||
static auto kMap = GeneratePluralRulesMap();
|
||||
ChoosePlural = kMap.value(languageId.toLower(), ChoosePluralEn);
|
||||
}
|
||||
|
||||
} // namespace Lang
|
||||
|
|
|
@ -23,6 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
namespace Lang {
|
||||
|
||||
QString Tag(const QString &original, ushort tag, const QString &replacement);
|
||||
QString Plural(ushort key0, ushort tag, float64 value);
|
||||
QString Plural(ushort keyBase, ushort tag, float64 value);
|
||||
void UpdatePluralRules(const QString &languageId);
|
||||
|
||||
} // namespace Lang
|
||||
|
|
|
@ -130,8 +130,10 @@ QString formatDurationText(qint64 duration) {
|
|||
QString formatDurationWords(qint64 duration) {
|
||||
if (duration > 59) {
|
||||
auto minutes = (duration / 60);
|
||||
auto minutesCount = lng_duration_minsec_minutes(lt_count, minutes);
|
||||
auto seconds = (duration % 60);
|
||||
return lng_duration_minutes_seconds(lt_count_minutes, minutes, lt_count_seconds, seconds);
|
||||
auto secondsCount = lng_duration_minsec_seconds(lt_count, seconds);
|
||||
return lng_duration_minutes_seconds(lt_minutes_count, minutesCount, lt_seconds_count, secondsCount);
|
||||
}
|
||||
return lng_duration_seconds(lt_count, duration);
|
||||
}
|
||||
|
|
|
@ -617,7 +617,7 @@ void MainWidget::updateForwardingTexts() {
|
|||
version += from->nameVersion;
|
||||
}
|
||||
if (fromUsers.size() > 2) {
|
||||
from = lng_forwarding_from(lt_user, fromUsers.at(0)->shortName(), lt_count, fromUsers.size() - 1);
|
||||
from = lng_forwarding_from(lt_count, fromUsers.size() - 1, lt_user, fromUsers.at(0)->shortName());
|
||||
} else if (fromUsers.size() < 2) {
|
||||
from = fromUsers.at(0)->name;
|
||||
} else {
|
||||
|
@ -4307,7 +4307,7 @@ bool MainWidget::usernameResolveFail(QString name, const RPCError &error) {
|
|||
void MainWidget::inviteCheckDone(QString hash, const MTPChatInvite &invite) {
|
||||
switch (invite.type()) {
|
||||
case mtpc_chatInvite: {
|
||||
auto &d(invite.c_chatInvite());
|
||||
auto &d = invite.c_chatInvite();
|
||||
|
||||
QVector<UserData*> participants;
|
||||
if (d.has_participants()) {
|
||||
|
@ -4320,7 +4320,8 @@ void MainWidget::inviteCheckDone(QString hash, const MTPChatInvite &invite) {
|
|||
}
|
||||
}
|
||||
_inviteHash = hash;
|
||||
Ui::show(Box<ConfirmInviteBox>(qs(d.vtitle), d.vphoto, d.vparticipants_count.v, participants));
|
||||
auto isChannel = d.is_channel() && !d.is_megagroup();
|
||||
Ui::show(Box<ConfirmInviteBox>(qs(d.vtitle), isChannel, d.vphoto, d.vparticipants_count.v, participants));
|
||||
} break;
|
||||
|
||||
case mtpc_chatInviteAlready: {
|
||||
|
|
|
@ -810,7 +810,7 @@ void OverviewInner::paintEvent(QPaintEvent *e) {
|
|||
} else if (_inSearch && _searchResults.isEmpty() && _searchFull && (!_migrated || _searchFullMigrated) && !_searchTimer.isActive()) {
|
||||
p.setFont(st::noContactsFont->f);
|
||||
p.setPen(st::noContactsColor->p);
|
||||
p.drawText(QRect(_rowsLeft, _marginTop, _rowWidth, _marginTop), lng_search_found_results(lt_count, 0), style::al_center);
|
||||
p.drawText(QRect(_rowsLeft, _marginTop, _rowWidth, _marginTop), lang(lng_search_no_results), style::al_center);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -373,23 +373,29 @@ void CoverWidget::refreshStatusText() {
|
|||
}
|
||||
|
||||
_cancelPhotoUpload.destroy();
|
||||
int currentTime = unixtime();
|
||||
auto currentTime = unixtime();
|
||||
if (_peerUser) {
|
||||
_statusText = App::onlineText(_peerUser, currentTime, true);
|
||||
_statusTextIsOnline = App::onlineColorUse(_peerUser, currentTime);
|
||||
} else if (_peerChat && _peerChat->amIn()) {
|
||||
int fullCount = qMax(_peerChat->count, _peerChat->participants.size());
|
||||
auto fullCount = qMax(_peerChat->count, _peerChat->participants.size());
|
||||
if (_onlineCount > 0 && _onlineCount <= fullCount) {
|
||||
_statusText = lng_chat_status_members_online(lt_count, fullCount, lt_count_online, _onlineCount);
|
||||
} else {
|
||||
auto membersCount = lng_chat_status_members(lt_count, fullCount);
|
||||
auto onlineCount = lng_chat_status_online(lt_count, _onlineCount);
|
||||
_statusText = lng_chat_status_members_online(lt_members_count, membersCount, lt_online_count, onlineCount);
|
||||
} else if (_peerChat->count > 0) {
|
||||
_statusText = lng_chat_status_members(lt_count, _peerChat->count);
|
||||
} else {
|
||||
_statusText = lang(lng_group_status);
|
||||
}
|
||||
} else if (_peerChannel) {
|
||||
int fullCount = _peerChannel->membersCount();
|
||||
auto fullCount = _peerChannel->membersCount();
|
||||
if (_onlineCount > 0 && _onlineCount <= fullCount) {
|
||||
_statusText = lng_chat_status_members_online(lt_count, fullCount, lt_count_online, _onlineCount);
|
||||
} else if (fullCount > 0 ) {
|
||||
_statusText = lng_chat_status_members(lt_count, _peerChannel->membersCount());
|
||||
auto membersCount = lng_chat_status_members(lt_count, fullCount);
|
||||
auto onlineCount = lng_chat_status_online(lt_count, _onlineCount);
|
||||
_statusText = lng_chat_status_members_online(lt_members_count, membersCount, lt_online_count, onlineCount);
|
||||
} else if (fullCount > 0) {
|
||||
_statusText = lng_chat_status_members(lt_count, fullCount);
|
||||
} else {
|
||||
_statusText = lang(_peerChannel->isMegagroup() ? lng_group_status : lng_channel_status);
|
||||
}
|
||||
|
|
|
@ -236,8 +236,8 @@ QString LastSeenPrivacyController::warning() {
|
|||
|
||||
QString LastSeenPrivacyController::exceptionLinkText(Exception exception, int count) {
|
||||
switch (exception) {
|
||||
case Exception::Always: return lng_edit_privacy_lastseen_always(lt_count, count);
|
||||
case Exception::Never: return lng_edit_privacy_lastseen_never(lt_count, count);
|
||||
case Exception::Always: return (count > 0) ? lng_edit_privacy_lastseen_always(lt_count, count) : lang(lng_edit_privacy_lastseen_always_empty);
|
||||
case Exception::Never: return (count > 0) ? lng_edit_privacy_lastseen_never(lt_count, count) : lang(lng_edit_privacy_lastseen_never_empty);
|
||||
}
|
||||
Unexpected("Invalid exception value.");
|
||||
}
|
||||
|
@ -290,8 +290,8 @@ QString GroupsInvitePrivacyController::description() {
|
|||
|
||||
QString GroupsInvitePrivacyController::exceptionLinkText(Exception exception, int count) {
|
||||
switch (exception) {
|
||||
case Exception::Always: return lng_edit_privacy_groups_always(lt_count, count);
|
||||
case Exception::Never: return lng_edit_privacy_groups_never(lt_count, count);
|
||||
case Exception::Always: return (count > 0) ? lng_edit_privacy_groups_always(lt_count, count) : lang(lng_edit_privacy_groups_always_empty);
|
||||
case Exception::Never: return (count > 0) ? lng_edit_privacy_groups_never(lt_count, count) : lang(lng_edit_privacy_groups_never_empty);
|
||||
}
|
||||
Unexpected("Invalid exception value.");
|
||||
}
|
||||
|
@ -322,8 +322,8 @@ QString CallsPrivacyController::description() {
|
|||
|
||||
QString CallsPrivacyController::exceptionLinkText(Exception exception, int count) {
|
||||
switch (exception) {
|
||||
case Exception::Always: return lng_edit_privacy_calls_always(lt_count, count);
|
||||
case Exception::Never: return lng_edit_privacy_calls_never(lt_count, count);
|
||||
case Exception::Always: return (count > 0) ? lng_edit_privacy_calls_always(lt_count, count) : lang(lng_edit_privacy_calls_always_empty);
|
||||
case Exception::Never: return (count > 0) ? lng_edit_privacy_calls_never(lt_count, count) : lang(lng_edit_privacy_calls_never_empty);
|
||||
}
|
||||
Unexpected("Invalid exception value.");
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
'<(PRODUCT_DIR)/codegen_style<(exe_ext)',
|
||||
'-I', '<(res_loc)', '-I', '<(src_loc)',
|
||||
'-o', '<(SHARED_INTERMEDIATE_DIR)/styles',
|
||||
'-w', '<(PRODUCT_DIR)/../..',
|
||||
'-w', '<(PRODUCT_DIR)/..',
|
||||
|
||||
# GYP/Ninja bug workaround: if we specify just <(RULE_INPUT_PATH)
|
||||
# the <(RULE_INPUT_ROOT) variables won't be available in Ninja,
|
||||
|
@ -90,7 +90,7 @@
|
|||
'action': [
|
||||
'<(PRODUCT_DIR)/codegen_lang<(exe_ext)',
|
||||
'-o', '<(SHARED_INTERMEDIATE_DIR)', '<(res_loc)/langs/lang.strings',
|
||||
'-w', '<(PRODUCT_DIR)/../..',
|
||||
'-w', '<(PRODUCT_DIR)/..',
|
||||
],
|
||||
'message': 'codegen_lang-ing lang.strings..',
|
||||
'process_outputs_as_sources': 1,
|
||||
|
@ -107,7 +107,7 @@
|
|||
'action': [
|
||||
'<(PRODUCT_DIR)/codegen_numbers<(exe_ext)',
|
||||
'-o', '<(SHARED_INTERMEDIATE_DIR)', '<(res_loc)/numbers.txt',
|
||||
'-w', '<(PRODUCT_DIR)/../..',
|
||||
'-w', '<(PRODUCT_DIR)/..',
|
||||
],
|
||||
'message': 'codegen_numbers-ing numbers.txt..',
|
||||
'process_outputs_as_sources': 1,
|
||||
|
@ -158,7 +158,7 @@
|
|||
'<(PRODUCT_DIR)/codegen_style<(exe_ext)',
|
||||
'-I', '<(res_loc)', '-I', '<(src_loc)',
|
||||
'-o', '<(SHARED_INTERMEDIATE_DIR)/styles',
|
||||
'-w', '<(PRODUCT_DIR)/../..',
|
||||
'-w', '<(PRODUCT_DIR)/..',
|
||||
|
||||
# GYP/Ninja bug workaround: if we specify just <(RULE_INPUT_PATH)
|
||||
# the <(RULE_INPUT_ROOT) variables won't be available in Ninja,
|
||||
|
|
Loading…
Reference in New Issue