diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index f44632402914b6169cc220ff9bd6221f0188021e..9547614bf47c41fb29c3d141c4b9063f38fc87d7 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -21,6 +21,7 @@ HOW TO WRITE A GOOD PULL REQUEST? ### Closes Issue(s) +closes #... <!-- List here all the issues closed by this pull request. Use keyword `closes` before each issue number --> ### Motivation diff --git a/akka-bbb-apps/src/main/java/.gitkeep b/akka-bbb-apps/src/main/java/.gitkeep deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/akka-bbb-apps/src/main/java/name/fraser/neil/plaintext/diff_match_patch.java b/akka-bbb-apps/src/main/java/name/fraser/neil/plaintext/diff_match_patch.java deleted file mode 100644 index 24ffdacacaf03ec0f0c3036f6f6f5f4e826942c2..0000000000000000000000000000000000000000 --- a/akka-bbb-apps/src/main/java/name/fraser/neil/plaintext/diff_match_patch.java +++ /dev/null @@ -1,2420 +0,0 @@ -/* - * Diff Match and Patch - * - * Copyright 2006 Google Inc. - * http://code.google.com/p/google-diff-match-patch/ - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package name.fraser.neil.plaintext; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.net.URLDecoder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Set; -import java.util.Stack; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - - -/* - * Functions for diff, match and patch. - * Computes the difference between two texts to create a patch. - * Applies the patch onto another text, allowing for errors. - * - * @author fraser@google.com (Neil Fraser) - */ - -/** - * Class containing the diff, match and patch methods. - * Also contains the behaviour settings. - */ -public class diff_match_patch { - - // Defaults. - // Set these on your diff_match_patch instance to override the defaults. - - /** - * Number of seconds to map a diff before giving up (0 for infinity). - */ - public float Diff_Timeout = 1.0f; - /** - * Cost of an empty edit operation in terms of edit characters. - */ - public short Diff_EditCost = 4; - /** - * The size beyond which the double-ended diff activates. - * Double-ending is twice as fast, but less accurate. - */ - public short Diff_DualThreshold = 32; - /** - * At what point is no match declared (0.0 = perfection, 1.0 = very loose). - */ - public float Match_Threshold = 0.5f; - /** - * How far to search for a match (0 = exact location, 1000+ = broad match). - * A match this many characters away from the expected location will add - * 1.0 to the score (0.0 is a perfect match). - */ - public int Match_Distance = 1000; - /** - * When deleting a large block of text (over ~64 characters), how close does - * the contents have to match the expected contents. (0.0 = perfection, - * 1.0 = very loose). Note that Match_Threshold controls how closely the - * end points of a delete need to match. - */ - public float Patch_DeleteThreshold = 0.5f; - /** - * Chunk size for context length. - */ - public short Patch_Margin = 4; - - /** - * The number of bits in an int. - */ - private int Match_MaxBits = 32; - - /** - * Internal class for returning results from diff_linesToChars(). - * Other less paranoid languages just use a three-element array. - */ - protected static class LinesToCharsResult { - protected String chars1; - protected String chars2; - protected List<String> lineArray; - - protected LinesToCharsResult(String chars1, String chars2, - List<String> lineArray) { - this.chars1 = chars1; - this.chars2 = chars2; - this.lineArray = lineArray; - } - } - - - // DIFF FUNCTIONS - - - /** - * The data structure representing a diff is a Linked list of Diff objects: - * {Diff(Operation.DELETE, "Hello"), Diff(Operation.INSERT, "Goodbye"), - * Diff(Operation.EQUAL, " world.")} - * which means: delete "Hello", add "Goodbye" and keep " world." - */ - public enum Operation { - DELETE, INSERT, EQUAL - } - - - /** - * Find the differences between two texts. - * Run a faster slightly less optimal diff - * This method allows the 'checklines' of diff_main() to be optional. - * Most of the time checklines is wanted, so default to true. - * @param text1 Old string to be diffed. - * @param text2 New string to be diffed. - * @return Linked List of Diff objects. - */ - public LinkedList<Diff> diff_main(String text1, String text2) { - return diff_main(text1, text2, true); - } - - /** - * Find the differences between two texts. Simplifies the problem by - * stripping any common prefix or suffix off the texts before diffing. - * @param text1 Old string to be diffed. - * @param text2 New string to be diffed. - * @param checklines Speedup flag. If false, then don't run a - * line-level diff first to identify the changed areas. - * If true, then run a faster slightly less optimal diff - * @return Linked List of Diff objects. - */ - public LinkedList<Diff> diff_main(String text1, String text2, - boolean checklines) { - // Check for equality (speedup) - LinkedList<Diff> diffs; - if (text1.equals(text2)) { - diffs = new LinkedList<Diff>(); - diffs.add(new Diff(Operation.EQUAL, text1)); - return diffs; - } - - // Trim off common prefix (speedup) - int commonlength = diff_commonPrefix(text1, text2); - String commonprefix = text1.substring(0, commonlength); - text1 = text1.substring(commonlength); - text2 = text2.substring(commonlength); - - // Trim off common suffix (speedup) - commonlength = diff_commonSuffix(text1, text2); - String commonsuffix = text1.substring(text1.length() - commonlength); - text1 = text1.substring(0, text1.length() - commonlength); - text2 = text2.substring(0, text2.length() - commonlength); - - // Compute the diff on the middle block - diffs = diff_compute(text1, text2, checklines); - - // Restore the prefix and suffix - if (commonprefix.length() != 0) { - diffs.addFirst(new Diff(Operation.EQUAL, commonprefix)); - } - if (commonsuffix.length() != 0) { - diffs.addLast(new Diff(Operation.EQUAL, commonsuffix)); - } - - diff_cleanupMerge(diffs); - return diffs; - } - - - /** - * Find the differences between two texts. Assumes that the texts do not - * have any common prefix or suffix. - * @param text1 Old string to be diffed. - * @param text2 New string to be diffed. - * @param checklines Speedup flag. If false, then don't run a - * line-level diff first to identify the changed areas. - * If true, then run a faster slightly less optimal diff - * @return Linked List of Diff objects. - */ - protected LinkedList<Diff> diff_compute(String text1, String text2, - boolean checklines) { - LinkedList<Diff> diffs = new LinkedList<Diff>(); - - if (text1.length() == 0) { - // Just add some text (speedup) - diffs.add(new Diff(Operation.INSERT, text2)); - return diffs; - } - - if (text2.length() == 0) { - // Just delete some text (speedup) - diffs.add(new Diff(Operation.DELETE, text1)); - return diffs; - } - - String longtext = text1.length() > text2.length() ? text1 : text2; - String shorttext = text1.length() > text2.length() ? text2 : text1; - int i = longtext.indexOf(shorttext); - if (i != -1) { - // Shorter text is inside the longer text (speedup) - Operation op = (text1.length() > text2.length()) ? - Operation.DELETE : Operation.INSERT; - diffs.add(new Diff(op, longtext.substring(0, i))); - diffs.add(new Diff(Operation.EQUAL, shorttext)); - diffs.add(new Diff(op, longtext.substring(i + shorttext.length()))); - return diffs; - } - longtext = shorttext = null; // Garbage collect. - - // Check to see if the problem can be split in two. - String[] hm = diff_halfMatch(text1, text2); - if (hm != null) { - // A half-match was found, sort out the return data. - String text1_a = hm[0]; - String text1_b = hm[1]; - String text2_a = hm[2]; - String text2_b = hm[3]; - String mid_common = hm[4]; - // Send both pairs off for separate processing. - LinkedList<Diff> diffs_a = diff_main(text1_a, text2_a, checklines); - LinkedList<Diff> diffs_b = diff_main(text1_b, text2_b, checklines); - // Merge the results. - diffs = diffs_a; - diffs.add(new Diff(Operation.EQUAL, mid_common)); - diffs.addAll(diffs_b); - return diffs; - } - - // Perform a real diff. - if (checklines && (text1.length() < 100 || text2.length() < 100)) { - checklines = false; // Too trivial for the overhead. - } - List<String> linearray = null; - if (checklines) { - // Scan the text on a line-by-line basis first. - LinesToCharsResult b = diff_linesToChars(text1, text2); - text1 = b.chars1; - text2 = b.chars2; - linearray = b.lineArray; - } - - diffs = diff_map(text1, text2); - if (diffs == null) { - // No acceptable result. - diffs = new LinkedList<Diff>(); - diffs.add(new Diff(Operation.DELETE, text1)); - diffs.add(new Diff(Operation.INSERT, text2)); - } - - if (checklines) { - // Convert the diff back to original text. - diff_charsToLines(diffs, linearray); - // Eliminate freak matches (e.g. blank lines) - diff_cleanupSemantic(diffs); - - // Rediff any replacement blocks, this time character-by-character. - // Add a dummy entry at the end. - diffs.add(new Diff(Operation.EQUAL, "")); - int count_delete = 0; - int count_insert = 0; - String text_delete = ""; - String text_insert = ""; - ListIterator<Diff> pointer = diffs.listIterator(); - Diff thisDiff = pointer.next(); - while (thisDiff != null) { - switch (thisDiff.operation) { - case INSERT: - count_insert++; - text_insert += thisDiff.text; - break; - case DELETE: - count_delete++; - text_delete += thisDiff.text; - break; - case EQUAL: - // Upon reaching an equality, check for prior redundancies. - if (count_delete >= 1 && count_insert >= 1) { - // Delete the offending records and add the merged ones. - pointer.previous(); - for (int j = 0; j < count_delete + count_insert; j++) { - pointer.previous(); - pointer.remove(); - } - for (Diff newDiff : diff_main(text_delete, text_insert, false)) { - pointer.add(newDiff); - } - } - count_insert = 0; - count_delete = 0; - text_delete = ""; - text_insert = ""; - break; - } - thisDiff = pointer.hasNext() ? pointer.next() : null; - } - diffs.removeLast(); // Remove the dummy entry at the end. - } - return diffs; - } - - - /** - * Split two texts into a list of strings. Reduce the texts to a string of - * hashes where each Unicode character represents one line. - * @param text1 First string. - * @param text2 Second string. - * @return An object containing the encoded text1, the encoded text2 and - * the List of unique strings. The zeroth element of the List of - * unique strings is intentionally blank. - */ - protected LinesToCharsResult diff_linesToChars(String text1, String text2) { - List<String> lineArray = new ArrayList<String>(); - Map<String, Integer> lineHash = new HashMap<String, Integer>(); - // e.g. linearray[4] == "Hello\n" - // e.g. linehash.get("Hello\n") == 4 - - // "\x00" is a valid character, but various debuggers don't like it. - // So we'll insert a junk entry to avoid generating a null character. - lineArray.add(""); - - String chars1 = diff_linesToCharsMunge(text1, lineArray, lineHash); - String chars2 = diff_linesToCharsMunge(text2, lineArray, lineHash); - return new LinesToCharsResult(chars1, chars2, lineArray); - } - - - /** - * Split a text into a list of strings. Reduce the texts to a string of - * hashes where each Unicode character represents one line. - * @param text String to encode. - * @param lineArray List of unique strings. - * @param lineHash Map of strings to indices. - * @return Encoded string. - */ - private String diff_linesToCharsMunge(String text, List<String> lineArray, - Map<String, Integer> lineHash) { - int lineStart = 0; - int lineEnd = -1; - String line; - StringBuilder chars = new StringBuilder(); - // Walk the text, pulling out a substring for each line. - // text.split('\n') would would temporarily double our memory footprint. - // Modifying text would create many large strings to garbage collect. - while (lineEnd < text.length() - 1) { - lineEnd = text.indexOf('\n', lineStart); - if (lineEnd == -1) { - lineEnd = text.length() - 1; - } - line = text.substring(lineStart, lineEnd + 1); - lineStart = lineEnd + 1; - - if (lineHash.containsKey(line)) { - chars.append(String.valueOf((char) (int) lineHash.get(line))); - } else { - lineArray.add(line); - lineHash.put(line, lineArray.size() - 1); - chars.append(String.valueOf((char) (lineArray.size() - 1))); - } - } - return chars.toString(); - } - - - /** - * Rehydrate the text in a diff from a string of line hashes to real lines of - * text. - * @param diffs LinkedList of Diff objects. - * @param lineArray List of unique strings. - */ - protected void diff_charsToLines(LinkedList<Diff> diffs, - List<String> lineArray) { - StringBuilder text; - for (Diff diff : diffs) { - text = new StringBuilder(); - for (int y = 0; y < diff.text.length(); y++) { - text.append(lineArray.get(diff.text.charAt(y))); - } - diff.text = text.toString(); - } - } - - - /** - * Explore the intersection points between the two texts. - * @param text1 Old string to be diffed. - * @param text2 New string to be diffed. - * @return LinkedList of Diff objects or null if no diff available. - */ - protected LinkedList<Diff> diff_map(String text1, String text2) { - long ms_end = System.currentTimeMillis() + (long) (Diff_Timeout * 1000); - // Cache the text lengths to prevent multiple calls. - int text1_length = text1.length(); - int text2_length = text2.length(); - int max_d = text1_length + text2_length - 1; - boolean doubleEnd = Diff_DualThreshold * 2 < max_d; - List<Set<Long>> v_map1 = new ArrayList<Set<Long>>(); - List<Set<Long>> v_map2 = new ArrayList<Set<Long>>(); - Map<Integer, Integer> v1 = new HashMap<Integer, Integer>(); - Map<Integer, Integer> v2 = new HashMap<Integer, Integer>(); - v1.put(1, 0); - v2.put(1, 0); - int x, y; - Long footstep = 0L; // Used to track overlapping paths. - Map<Long, Integer> footsteps = new HashMap<Long, Integer>(); - boolean done = false; - // If the total number of characters is odd, then the front path will - // collide with the reverse path. - boolean front = ((text1_length + text2_length) % 2 == 1); - for (int d = 0; d < max_d; d++) { - // Bail out if timeout reached. - if (Diff_Timeout > 0 && System.currentTimeMillis() > ms_end) { - return null; - } - - // Walk the front path one step. - v_map1.add(new HashSet<Long>()); // Adds at index 'd'. - for (int k = -d; k <= d; k += 2) { - if (k == -d || k != d && v1.get(k - 1) < v1.get(k + 1)) { - x = v1.get(k + 1); - } else { - x = v1.get(k - 1) + 1; - } - y = x - k; - if (doubleEnd) { - footstep = diff_footprint(x, y); - if (front && (footsteps.containsKey(footstep))) { - done = true; - } - if (!front) { - footsteps.put(footstep, d); - } - } - while (!done && x < text1_length && y < text2_length - && text1.charAt(x) == text2.charAt(y)) { - x++; - y++; - if (doubleEnd) { - footstep = diff_footprint(x, y); - if (front && (footsteps.containsKey(footstep))) { - done = true; - } - if (!front) { - footsteps.put(footstep, d); - } - } - } - v1.put(k, x); - v_map1.get(d).add(diff_footprint(x, y)); - if (x == text1_length && y == text2_length) { - // Reached the end in single-path mode. - return diff_path1(v_map1, text1, text2); - } else if (done) { - // Front path ran over reverse path. - v_map2 = v_map2.subList(0, footsteps.get(footstep) + 1); - LinkedList<Diff> a = diff_path1(v_map1, text1.substring(0, x), - text2.substring(0, y)); - a.addAll(diff_path2(v_map2, text1.substring(x), text2.substring(y))); - return a; - } - } - - if (doubleEnd) { - // Walk the reverse path one step. - v_map2.add(new HashSet<Long>()); // Adds at index 'd'. - for (int k = -d; k <= d; k += 2) { - if (k == -d || k != d && v2.get(k - 1) < v2.get(k + 1)) { - x = v2.get(k + 1); - } else { - x = v2.get(k - 1) + 1; - } - y = x - k; - footstep = diff_footprint(text1_length - x, text2_length - y); - if (!front && (footsteps.containsKey(footstep))) { - done = true; - } - if (front) { - footsteps.put(footstep, d); - } - while (!done && x < text1_length && y < text2_length - && text1.charAt(text1_length - x - 1) - == text2.charAt(text2_length - y - 1)) { - x++; - y++; - footstep = diff_footprint(text1_length - x, text2_length - y); - if (!front && (footsteps.containsKey(footstep))) { - done = true; - } - if (front) { - footsteps.put(footstep, d); - } - } - v2.put(k, x); - v_map2.get(d).add(diff_footprint(x, y)); - if (done) { - // Reverse path ran over front path. - v_map1 = v_map1.subList(0, footsteps.get(footstep) + 1); - LinkedList<Diff> a - = diff_path1(v_map1, text1.substring(0, text1_length - x), - text2.substring(0, text2_length - y)); - a.addAll(diff_path2(v_map2, text1.substring(text1_length - x), - text2.substring(text2_length - y))); - return a; - } - } - } - } - // Number of diffs equals number of characters, no commonality at all. - return null; - } - - - /** - * Work from the middle back to the start to determine the path. - * @param v_map List of path sets. - * @param text1 Old string fragment to be diffed. - * @param text2 New string fragment to be diffed. - * @return LinkedList of Diff objects. - */ - protected LinkedList<Diff> diff_path1(List<Set<Long>> v_map, - String text1, String text2) { - LinkedList<Diff> path = new LinkedList<Diff>(); - int x = text1.length(); - int y = text2.length(); - Operation last_op = null; - for (int d = v_map.size() - 2; d >= 0; d--) { - while (true) { - if (v_map.get(d).contains(diff_footprint(x - 1, y))) { - x--; - if (last_op == Operation.DELETE) { - path.getFirst().text = text1.charAt(x) + path.getFirst().text; - } else { - path.addFirst(new Diff(Operation.DELETE, - text1.substring(x, x + 1))); - } - last_op = Operation.DELETE; - break; - } else if (v_map.get(d).contains(diff_footprint(x, y - 1))) { - y--; - if (last_op == Operation.INSERT) { - path.getFirst().text = text2.charAt(y) + path.getFirst().text; - } else { - path.addFirst(new Diff(Operation.INSERT, - text2.substring(y, y + 1))); - } - last_op = Operation.INSERT; - break; - } else { - x--; - y--; - assert (text1.charAt(x) == text2.charAt(y)) - : "No diagonal. Can't happen. (diff_path1)"; - if (last_op == Operation.EQUAL) { - path.getFirst().text = text1.charAt(x) + path.getFirst().text; - } else { - path.addFirst(new Diff(Operation.EQUAL, text1.substring(x, x + 1))); - } - last_op = Operation.EQUAL; - } - } - } - return path; - } - - - /** - * Work from the middle back to the end to determine the path. - * @param v_map List of path sets. - * @param text1 Old string fragment to be diffed. - * @param text2 New string fragment to be diffed. - * @return LinkedList of Diff objects. - */ - protected LinkedList<Diff> diff_path2(List<Set<Long>> v_map, - String text1, String text2) { - LinkedList<Diff> path = new LinkedList<Diff>(); - int x = text1.length(); - int y = text2.length(); - Operation last_op = null; - for (int d = v_map.size() - 2; d >= 0; d--) { - while (true) { - if (v_map.get(d).contains(diff_footprint(x - 1, y))) { - x--; - if (last_op == Operation.DELETE) { - path.getLast().text += text1.charAt(text1.length() - x - 1); - } else { - path.addLast(new Diff(Operation.DELETE, - text1.substring(text1.length() - x - 1, text1.length() - x))); - } - last_op = Operation.DELETE; - break; - } else if (v_map.get(d).contains(diff_footprint(x, y - 1))) { - y--; - if (last_op == Operation.INSERT) { - path.getLast().text += text2.charAt(text2.length() - y - 1); - } else { - path.addLast(new Diff(Operation.INSERT, - text2.substring(text2.length() - y - 1, text2.length() - y))); - } - last_op = Operation.INSERT; - break; - } else { - x--; - y--; - assert (text1.charAt(text1.length() - x - 1) - == text2.charAt(text2.length() - y - 1)) - : "No diagonal. Can't happen. (diff_path2)"; - if (last_op == Operation.EQUAL) { - path.getLast().text += text1.charAt(text1.length() - x - 1); - } else { - path.addLast(new Diff(Operation.EQUAL, - text1.substring(text1.length() - x - 1, text1.length() - x))); - } - last_op = Operation.EQUAL; - } - } - } - return path; - } - - - /** - * Compute a good hash of two integers. - * @param x First int. - * @param y Second int. - * @return A long made up of both ints. - */ - protected long diff_footprint(int x, int y) { - // The maximum size for a long is 9,223,372,036,854,775,807 - // The maximum size for an int is 2,147,483,647 - // Two ints fit nicely in one long. - long result = x; - result = result << 32; - result += y; - return result; - } - - - /** - * Determine the common prefix of two strings - * @param text1 First string. - * @param text2 Second string. - * @return The number of characters common to the start of each string. - */ - public int diff_commonPrefix(String text1, String text2) { - // Performance analysis: http://neil.fraser.name/news/2007/10/09/ - int n = Math.min(text1.length(), text2.length()); - for (int i = 0; i < n; i++) { - if (text1.charAt(i) != text2.charAt(i)) { - return i; - } - } - return n; - } - - - /** - * Determine the common suffix of two strings - * @param text1 First string. - * @param text2 Second string. - * @return The number of characters common to the end of each string. - */ - public int diff_commonSuffix(String text1, String text2) { - // Performance analysis: http://neil.fraser.name/news/2007/10/09/ - int text1_length = text1.length(); - int text2_length = text2.length(); - int n = Math.min(text1_length, text2_length); - for (int i = 1; i <= n; i++) { - if (text1.charAt(text1_length - i) != text2.charAt(text2_length - i)) { - return i - 1; - } - } - return n; - } - - - /** - * Do the two texts share a substring which is at least half the length of - * the longer text? - * @param text1 First string. - * @param text2 Second string. - * @return Five element String array, containing the prefix of text1, the - * suffix of text1, the prefix of text2, the suffix of text2 and the - * common middle. Or null if there was no match. - */ - protected String[] diff_halfMatch(String text1, String text2) { - String longtext = text1.length() > text2.length() ? text1 : text2; - String shorttext = text1.length() > text2.length() ? text2 : text1; - if (longtext.length() < 10 || shorttext.length() < 1) { - return null; // Pointless. - } - - // First check if the second quarter is the seed for a half-match. - String[] hm1 = diff_halfMatchI(longtext, shorttext, - (longtext.length() + 3) / 4); - // Check again based on the third quarter. - String[] hm2 = diff_halfMatchI(longtext, shorttext, - (longtext.length() + 1) / 2); - String[] hm; - if (hm1 == null && hm2 == null) { - return null; - } else if (hm2 == null) { - hm = hm1; - } else if (hm1 == null) { - hm = hm2; - } else { - // Both matched. Select the longest. - hm = hm1[4].length() > hm2[4].length() ? hm1 : hm2; - } - - // A half-match was found, sort out the return data. - if (text1.length() > text2.length()) { - return hm; - //return new String[]{hm[0], hm[1], hm[2], hm[3], hm[4]}; - } else { - return new String[]{hm[2], hm[3], hm[0], hm[1], hm[4]}; - } - } - - - /** - * Does a substring of shorttext exist within longtext such that the - * substring is at least half the length of longtext? - * @param longtext Longer string. - * @param shorttext Shorter string. - * @param i Start index of quarter length substring within longtext. - * @return Five element String array, containing the prefix of longtext, the - * suffix of longtext, the prefix of shorttext, the suffix of shorttext - * and the common middle. Or null if there was no match. - */ - private String[] diff_halfMatchI(String longtext, String shorttext, int i) { - // Start with a 1/4 length substring at position i as a seed. - String seed = longtext.substring(i, i + longtext.length() / 4); - int j = -1; - String best_common = ""; - String best_longtext_a = "", best_longtext_b = ""; - String best_shorttext_a = "", best_shorttext_b = ""; - while ((j = shorttext.indexOf(seed, j + 1)) != -1) { - int prefixLength = diff_commonPrefix(longtext.substring(i), - shorttext.substring(j)); - int suffixLength = diff_commonSuffix(longtext.substring(0, i), - shorttext.substring(0, j)); - if (best_common.length() < suffixLength + prefixLength) { - best_common = shorttext.substring(j - suffixLength, j) - + shorttext.substring(j, j + prefixLength); - best_longtext_a = longtext.substring(0, i - suffixLength); - best_longtext_b = longtext.substring(i + prefixLength); - best_shorttext_a = shorttext.substring(0, j - suffixLength); - best_shorttext_b = shorttext.substring(j + prefixLength); - } - } - if (best_common.length() >= longtext.length() / 2) { - return new String[]{best_longtext_a, best_longtext_b, - best_shorttext_a, best_shorttext_b, best_common}; - } else { - return null; - } - } - - - /** - * Reduce the number of edits by eliminating semantically trivial equalities. - * @param diffs LinkedList of Diff objects. - */ - public void diff_cleanupSemantic(LinkedList<Diff> diffs) { - if (diffs.isEmpty()) { - return; - } - boolean changes = false; - Stack<Diff> equalities = new Stack<Diff>(); // Stack of qualities. - String lastequality = null; // Always equal to equalities.lastElement().text - ListIterator<Diff> pointer = diffs.listIterator(); - // Number of characters that changed prior to the equality. - int length_changes1 = 0; - // Number of characters that changed after the equality. - int length_changes2 = 0; - Diff thisDiff = pointer.next(); - while (thisDiff != null) { - if (thisDiff.operation == Operation.EQUAL) { - // equality found - equalities.push(thisDiff); - length_changes1 = length_changes2; - length_changes2 = 0; - lastequality = thisDiff.text; - } else { - // an insertion or deletion - length_changes2 += thisDiff.text.length(); - if (lastequality != null && (lastequality.length() <= length_changes1) - && (lastequality.length() <= length_changes2)) { - //System.out.println("Splitting: '" + lastequality + "'"); - // Walk back to offending equality. - while (thisDiff != equalities.lastElement()) { - thisDiff = pointer.previous(); - } - pointer.next(); - - // Replace equality with a delete. - pointer.set(new Diff(Operation.DELETE, lastequality)); - // Insert a corresponding an insert. - pointer.add(new Diff(Operation.INSERT, lastequality)); - - equalities.pop(); // Throw away the equality we just deleted. - if (!equalities.empty()) { - // Throw away the previous equality (it needs to be reevaluated). - equalities.pop(); - } - if (equalities.empty()) { - // There are no previous equalities, walk back to the start. - while (pointer.hasPrevious()) { - pointer.previous(); - } - } else { - // There is a safe equality we can fall back to. - thisDiff = equalities.lastElement(); - while (thisDiff != pointer.previous()) { - // Intentionally empty loop. - } - } - - length_changes1 = 0; // Reset the counters. - length_changes2 = 0; - lastequality = null; - changes = true; - } - } - thisDiff = pointer.hasNext() ? pointer.next() : null; - } - - if (changes) { - diff_cleanupMerge(diffs); - } - diff_cleanupSemanticLossless(diffs); - } - - - /** - * Look for single edits surrounded on both sides by equalities - * which can be shifted sideways to align the edit to a word boundary. - * e.g: The c<ins>at c</ins>ame. -> The <ins>cat </ins>came. - * @param diffs LinkedList of Diff objects. - */ - public void diff_cleanupSemanticLossless(LinkedList<Diff> diffs) { - String equality1, edit, equality2; - String commonString; - int commonOffset; - int score, bestScore; - String bestEquality1, bestEdit, bestEquality2; - // Create a new iterator at the start. - ListIterator<Diff> pointer = diffs.listIterator(); - Diff prevDiff = pointer.hasNext() ? pointer.next() : null; - Diff thisDiff = pointer.hasNext() ? pointer.next() : null; - Diff nextDiff = pointer.hasNext() ? pointer.next() : null; - // Intentionally ignore the first and last element (don't need checking). - while (nextDiff != null) { - if (prevDiff.operation == Operation.EQUAL && - nextDiff.operation == Operation.EQUAL) { - // This is a single edit surrounded by equalities. - equality1 = prevDiff.text; - edit = thisDiff.text; - equality2 = nextDiff.text; - - // First, shift the edit as far left as possible. - commonOffset = diff_commonSuffix(equality1, edit); - if (commonOffset != 0) { - commonString = edit.substring(edit.length() - commonOffset); - equality1 = equality1.substring(0, equality1.length() - commonOffset); - edit = commonString + edit.substring(0, edit.length() - commonOffset); - equality2 = commonString + equality2; - } - - // Second, step character by character right, looking for the best fit. - bestEquality1 = equality1; - bestEdit = edit; - bestEquality2 = equality2; - bestScore = diff_cleanupSemanticScore(equality1, edit) - + diff_cleanupSemanticScore(edit, equality2); - while (edit.length() != 0 && equality2.length() != 0 - && edit.charAt(0) == equality2.charAt(0)) { - equality1 += edit.charAt(0); - edit = edit.substring(1) + equality2.charAt(0); - equality2 = equality2.substring(1); - score = diff_cleanupSemanticScore(equality1, edit) - + diff_cleanupSemanticScore(edit, equality2); - // The >= encourages trailing rather than leading whitespace on edits. - if (score >= bestScore) { - bestScore = score; - bestEquality1 = equality1; - bestEdit = edit; - bestEquality2 = equality2; - } - } - - if (!prevDiff.text.equals(bestEquality1)) { - // We have an improvement, save it back to the diff. - if (bestEquality1.length() != 0) { - prevDiff.text = bestEquality1; - } else { - pointer.previous(); // Walk past nextDiff. - pointer.previous(); // Walk past thisDiff. - pointer.previous(); // Walk past prevDiff. - pointer.remove(); // Delete prevDiff. - pointer.next(); // Walk past thisDiff. - pointer.next(); // Walk past nextDiff. - } - thisDiff.text = bestEdit; - if (bestEquality2.length() != 0) { - nextDiff.text = bestEquality2; - } else { - pointer.remove(); // Delete nextDiff. - nextDiff = thisDiff; - thisDiff = prevDiff; - } - } - } - prevDiff = thisDiff; - thisDiff = nextDiff; - nextDiff = pointer.hasNext() ? pointer.next() : null; - } - } - - - /** - * Given two strings, compute a score representing whether the internal - * boundary falls on logical boundaries. - * Scores range from 5 (best) to 0 (worst). - * @param one First string. - * @param two Second string. - * @return The score. - */ - private int diff_cleanupSemanticScore(String one, String two) { - if (one.length() == 0 || two.length() == 0) { - // Edges are the best. - return 5; - } - - // Each port of this function behaves slightly differently due to - // subtle differences in each language's definition of things like - // 'whitespace'. Since this function's purpose is largely cosmetic, - // the choice has been made to use each language's native features - // rather than force total conformity. - int score = 0; - // One point for non-alphanumeric. - if (!Character.isLetterOrDigit(one.charAt(one.length() - 1)) - || !Character.isLetterOrDigit(two.charAt(0))) { - score++; - // Two points for whitespace. - if (Character.isWhitespace(one.charAt(one.length() - 1)) - || Character.isWhitespace(two.charAt(0))) { - score++; - // Three points for line breaks. - if (Character.getType(one.charAt(one.length() - 1)) == Character.CONTROL - || Character.getType(two.charAt(0)) == Character.CONTROL) { - score++; - // Four points for blank lines. - if (BLANKLINEEND.matcher(one).find() - || BLANKLINESTART.matcher(two).find()) { - score++; - } - } - } - } - return score; - } - - - private Pattern BLANKLINEEND - = Pattern.compile("\\n\\r?\\n\\Z", Pattern.DOTALL); - private Pattern BLANKLINESTART - = Pattern.compile("\\A\\r?\\n\\r?\\n", Pattern.DOTALL); - - - /** - * Reduce the number of edits by eliminating operationally trivial equalities. - * @param diffs LinkedList of Diff objects. - */ - public void diff_cleanupEfficiency(LinkedList<Diff> diffs) { - if (diffs.isEmpty()) { - return; - } - boolean changes = false; - Stack<Diff> equalities = new Stack<Diff>(); // Stack of equalities. - String lastequality = null; // Always equal to equalities.lastElement().text - ListIterator<Diff> pointer = diffs.listIterator(); - // Is there an insertion operation before the last equality. - boolean pre_ins = false; - // Is there a deletion operation before the last equality. - boolean pre_del = false; - // Is there an insertion operation after the last equality. - boolean post_ins = false; - // Is there a deletion operation after the last equality. - boolean post_del = false; - Diff thisDiff = pointer.next(); - Diff safeDiff = thisDiff; // The last Diff that is known to be unsplitable. - while (thisDiff != null) { - if (thisDiff.operation == Operation.EQUAL) { - // equality found - if (thisDiff.text.length() < Diff_EditCost && (post_ins || post_del)) { - // Candidate found. - equalities.push(thisDiff); - pre_ins = post_ins; - pre_del = post_del; - lastequality = thisDiff.text; - } else { - // Not a candidate, and can never become one. - equalities.clear(); - lastequality = null; - safeDiff = thisDiff; - } - post_ins = post_del = false; - } else { - // an insertion or deletion - if (thisDiff.operation == Operation.DELETE) { - post_del = true; - } else { - post_ins = true; - } - /* - * Five types to be split: - * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del> - * <ins>A</ins>X<ins>C</ins><del>D</del> - * <ins>A</ins><del>B</del>X<ins>C</ins> - * <ins>A</del>X<ins>C</ins><del>D</del> - * <ins>A</ins><del>B</del>X<del>C</del> - */ - if (lastequality != null - && ((pre_ins && pre_del && post_ins && post_del) - || ((lastequality.length() < Diff_EditCost / 2) - && ((pre_ins ? 1 : 0) + (pre_del ? 1 : 0) - + (post_ins ? 1 : 0) + (post_del ? 1 : 0)) == 3))) { - //System.out.println("Splitting: '" + lastequality + "'"); - // Walk back to offending equality. - while (thisDiff != equalities.lastElement()) { - thisDiff = pointer.previous(); - } - pointer.next(); - - // Replace equality with a delete. - pointer.set(new Diff(Operation.DELETE, lastequality)); - // Insert a corresponding an insert. - pointer.add(thisDiff = new Diff(Operation.INSERT, lastequality)); - - equalities.pop(); // Throw away the equality we just deleted. - lastequality = null; - if (pre_ins && pre_del) { - // No changes made which could affect previous entry, keep going. - post_ins = post_del = true; - equalities.clear(); - safeDiff = thisDiff; - } else { - if (!equalities.empty()) { - // Throw away the previous equality (it needs to be reevaluated). - equalities.pop(); - } - if (equalities.empty()) { - // There are no previous questionable equalities, - // walk back to the last known safe diff. - thisDiff = safeDiff; - } else { - // There is an equality we can fall back to. - thisDiff = equalities.lastElement(); - } - while (thisDiff != pointer.previous()) { - // Intentionally empty loop. - } - post_ins = post_del = false; - } - - changes = true; - } - } - thisDiff = pointer.hasNext() ? pointer.next() : null; - } - - if (changes) { - diff_cleanupMerge(diffs); - } - } - - - /** - * Reorder and merge like edit sections. Merge equalities. - * Any edit section can move as long as it doesn't cross an equality. - * @param diffs LinkedList of Diff objects. - */ - public void diff_cleanupMerge(LinkedList<Diff> diffs) { - diffs.add(new Diff(Operation.EQUAL, "")); // Add a dummy entry at the end. - ListIterator<Diff> pointer = diffs.listIterator(); - int count_delete = 0; - int count_insert = 0; - String text_delete = ""; - String text_insert = ""; - Diff thisDiff = pointer.next(); - Diff prevEqual = null; - int commonlength; - while (thisDiff != null) { - switch (thisDiff.operation) { - case INSERT: - count_insert++; - text_insert += thisDiff.text; - prevEqual = null; - break; - case DELETE: - count_delete++; - text_delete += thisDiff.text; - prevEqual = null; - break; - case EQUAL: - if (count_delete != 0 || count_insert != 0) { - // Delete the offending records. - pointer.previous(); // Reverse direction. - while (count_delete-- > 0) { - pointer.previous(); - pointer.remove(); - } - while (count_insert-- > 0) { - pointer.previous(); - pointer.remove(); - } - if (count_delete != 0 && count_insert != 0) { - // Factor out any common prefixies. - commonlength = diff_commonPrefix(text_insert, text_delete); - if (commonlength != 0) { - if (pointer.hasPrevious()) { - thisDiff = pointer.previous(); - assert thisDiff.operation == Operation.EQUAL - : "Previous diff should have been an equality."; - thisDiff.text += text_insert.substring(0, commonlength); - pointer.next(); - } else { - pointer.add(new Diff(Operation.EQUAL, - text_insert.substring(0, commonlength))); - } - text_insert = text_insert.substring(commonlength); - text_delete = text_delete.substring(commonlength); - } - // Factor out any common suffixies. - commonlength = diff_commonSuffix(text_insert, text_delete); - if (commonlength != 0) { - thisDiff = pointer.next(); - thisDiff.text = text_insert.substring(text_insert.length() - - commonlength) + thisDiff.text; - text_insert = text_insert.substring(0, text_insert.length() - - commonlength); - text_delete = text_delete.substring(0, text_delete.length() - - commonlength); - pointer.previous(); - } - } - // Insert the merged records. - if (text_delete.length() != 0) { - pointer.add(new Diff(Operation.DELETE, text_delete)); - } - if (text_insert.length() != 0) { - pointer.add(new Diff(Operation.INSERT, text_insert)); - } - // Step forward to the equality. - thisDiff = pointer.hasNext() ? pointer.next() : null; - } else if (prevEqual != null) { - // Merge this equality with the previous one. - prevEqual.text += thisDiff.text; - pointer.remove(); - thisDiff = pointer.previous(); - pointer.next(); // Forward direction - } - count_insert = 0; - count_delete = 0; - text_delete = ""; - text_insert = ""; - prevEqual = thisDiff; - break; - } - thisDiff = pointer.hasNext() ? pointer.next() : null; - } - // System.out.println(diff); - if (diffs.getLast().text.length() == 0) { - diffs.removeLast(); // Remove the dummy entry at the end. - } - - /* - * Second pass: look for single edits surrounded on both sides by equalities - * which can be shifted sideways to eliminate an equality. - * e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC - */ - boolean changes = false; - // Create a new iterator at the start. - // (As opposed to walking the current one back.) - pointer = diffs.listIterator(); - Diff prevDiff = pointer.hasNext() ? pointer.next() : null; - thisDiff = pointer.hasNext() ? pointer.next() : null; - Diff nextDiff = pointer.hasNext() ? pointer.next() : null; - // Intentionally ignore the first and last element (don't need checking). - while (nextDiff != null) { - if (prevDiff.operation == Operation.EQUAL && - nextDiff.operation == Operation.EQUAL) { - // This is a single edit surrounded by equalities. - if (thisDiff.text.endsWith(prevDiff.text)) { - // Shift the edit over the previous equality. - thisDiff.text = prevDiff.text - + thisDiff.text.substring(0, thisDiff.text.length() - - prevDiff.text.length()); - nextDiff.text = prevDiff.text + nextDiff.text; - pointer.previous(); // Walk past nextDiff. - pointer.previous(); // Walk past thisDiff. - pointer.previous(); // Walk past prevDiff. - pointer.remove(); // Delete prevDiff. - pointer.next(); // Walk past thisDiff. - thisDiff = pointer.next(); // Walk past nextDiff. - nextDiff = pointer.hasNext() ? pointer.next() : null; - changes = true; - } else if (thisDiff.text.startsWith(nextDiff.text)) { - // Shift the edit over the next equality. - prevDiff.text += nextDiff.text; - thisDiff.text = thisDiff.text.substring(nextDiff.text.length()) - + nextDiff.text; - pointer.remove(); // Delete nextDiff. - nextDiff = pointer.hasNext() ? pointer.next() : null; - changes = true; - } - } - prevDiff = thisDiff; - thisDiff = nextDiff; - nextDiff = pointer.hasNext() ? pointer.next() : null; - } - // If shifts were made, the diff needs reordering and another shift sweep. - if (changes) { - diff_cleanupMerge(diffs); - } - } - - - /** - * loc is a location in text1, compute and return the equivalent location in - * text2. - * e.g. "The cat" vs "The big cat", 1->1, 5->8 - * @param diffs LinkedList of Diff objects. - * @param loc Location within text1. - * @return Location within text2. - */ - public int diff_xIndex(LinkedList<Diff> diffs, int loc) { - int chars1 = 0; - int chars2 = 0; - int last_chars1 = 0; - int last_chars2 = 0; - Diff lastDiff = null; - for (Diff aDiff : diffs) { - if (aDiff.operation != Operation.INSERT) { - // Equality or deletion. - chars1 += aDiff.text.length(); - } - if (aDiff.operation != Operation.DELETE) { - // Equality or insertion. - chars2 += aDiff.text.length(); - } - if (chars1 > loc) { - // Overshot the location. - lastDiff = aDiff; - break; - } - last_chars1 = chars1; - last_chars2 = chars2; - } - if (lastDiff != null && lastDiff.operation == Operation.DELETE) { - // The location was deleted. - return last_chars2; - } - // Add the remaining character length. - return last_chars2 + (loc - last_chars1); - } - - - /** - * Convert a Diff list into a pretty HTML report. - * @param diffs LinkedList of Diff objects. - * @return HTML representation. - */ - public String diff_prettyHtml(LinkedList<Diff> diffs) { - StringBuilder html = new StringBuilder(); - int i = 0; - for (Diff aDiff : diffs) { - String text = aDiff.text.replace("&", "&").replace("<", "<") - .replace(">", ">").replace("\n", "¶<BR>"); - switch (aDiff.operation) { - case INSERT: - html.append("<INS STYLE=\"background:#E6FFE6;\" TITLE=\"i=").append(i) - .append("\">").append(text).append("</INS>"); - break; - case DELETE: - html.append("<DEL STYLE=\"background:#FFE6E6;\" TITLE=\"i=").append(i) - .append("\">").append(text).append("</DEL>"); - break; - case EQUAL: - html.append("<SPAN TITLE=\"i=").append(i).append("\">").append(text) - .append("</SPAN>"); - break; - } - if (aDiff.operation != Operation.DELETE) { - i += aDiff.text.length(); - } - } - return html.toString(); - } - - - /** - * Compute and return the source text (all equalities and deletions). - * @param diffs LinkedList of Diff objects. - * @return Source text. - */ - public String diff_text1(LinkedList<Diff> diffs) { - StringBuilder text = new StringBuilder(); - for (Diff aDiff : diffs) { - if (aDiff.operation != Operation.INSERT) { - text.append(aDiff.text); - } - } - return text.toString(); - } - - - /** - * Compute and return the destination text (all equalities and insertions). - * @param diffs LinkedList of Diff objects. - * @return Destination text. - */ - public String diff_text2(LinkedList<Diff> diffs) { - StringBuilder text = new StringBuilder(); - for (Diff aDiff : diffs) { - if (aDiff.operation != Operation.DELETE) { - text.append(aDiff.text); - } - } - return text.toString(); - } - - - /** - * Compute the Levenshtein distance; the number of inserted, deleted or - * substituted characters. - * @param diffs LinkedList of Diff objects. - * @return Number of changes. - */ - public int diff_levenshtein(LinkedList<Diff> diffs) { - int levenshtein = 0; - int insertions = 0; - int deletions = 0; - for (Diff aDiff : diffs) { - switch (aDiff.operation) { - case INSERT: - insertions += aDiff.text.length(); - break; - case DELETE: - deletions += aDiff.text.length(); - break; - case EQUAL: - // A deletion and an insertion is one substitution. - levenshtein += Math.max(insertions, deletions); - insertions = 0; - deletions = 0; - break; - } - } - levenshtein += Math.max(insertions, deletions); - return levenshtein; - } - - - /** - * Crush the diff into an encoded string which describes the operations - * required to transform text1 into text2. - * E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'. - * Operations are tab-separated. Inserted text is escaped using %xx notation. - * @param diffs Array of diff tuples. - * @return Delta text. - */ - public String diff_toDelta(LinkedList<Diff> diffs) { - StringBuilder text = new StringBuilder(); - for (Diff aDiff : diffs) { - switch (aDiff.operation) { - case INSERT: - try { - text.append("+").append(URLEncoder.encode(aDiff.text, "UTF-8") - .replace('+', ' ')).append("\t"); - } catch (UnsupportedEncodingException e) { - // Not likely on modern system. - throw new Error("This system does not support UTF-8.", e); - } - break; - case DELETE: - text.append("-").append(aDiff.text.length()).append("\t"); - break; - case EQUAL: - text.append("=").append(aDiff.text.length()).append("\t"); - break; - } - } - String delta = text.toString(); - if (delta.length() != 0) { - // Strip off trailing tab character. - delta = delta.substring(0, delta.length() - 1); - delta = unescapeForEncodeUriCompatability(delta); - } - return delta; - } - - - /** - * Given the original text1, and an encoded string which describes the - * operations required to transform text1 into text2, compute the full diff. - * @param text1 Source string for the diff. - * @param delta Delta text. - * @return Array of diff tuples or null if invalid. - * @throws IllegalArgumentException If invalid input. - */ - public LinkedList<Diff> diff_fromDelta(String text1, String delta) - throws IllegalArgumentException { - LinkedList<Diff> diffs = new LinkedList<Diff>(); - int pointer = 0; // Cursor in text1 - String[] tokens = delta.split("\t"); - for (String token : tokens) { - if (token.length() == 0) { - // Blank tokens are ok (from a trailing \t). - continue; - } - // Each token begins with a one character parameter which specifies the - // operation of this token (delete, insert, equality). - String param = token.substring(1); - switch (token.charAt(0)) { - case '+': - // decode would change all "+" to " " - param = param.replace("+", "%2B"); - try { - param = URLDecoder.decode(param, "UTF-8"); - } catch (UnsupportedEncodingException e) { - // Not likely on modern system. - throw new Error("This system does not support UTF-8.", e); - } catch (IllegalArgumentException e) { - // Malformed URI sequence. - throw new IllegalArgumentException( - "Illegal escape in diff_fromDelta: " + param, e); - } - diffs.add(new Diff(Operation.INSERT, param)); - break; - case '-': - // Fall through. - case '=': - int n; - try { - n = Integer.parseInt(param); - } catch (NumberFormatException e) { - throw new IllegalArgumentException( - "Invalid number in diff_fromDelta: " + param, e); - } - if (n < 0) { - throw new IllegalArgumentException( - "Negative number in diff_fromDelta: " + param); - } - String text; - try { - text = text1.substring(pointer, pointer += n); - } catch (StringIndexOutOfBoundsException e) { - throw new IllegalArgumentException("Delta length (" + pointer - + ") larger than source text length (" + text1.length() - + ").", e); - } - if (token.charAt(0) == '=') { - diffs.add(new Diff(Operation.EQUAL, text)); - } else { - diffs.add(new Diff(Operation.DELETE, text)); - } - break; - default: - // Anything else is an error. - throw new IllegalArgumentException( - "Invalid diff operation in diff_fromDelta: " + token.charAt(0)); - } - } - if (pointer != text1.length()) { - throw new IllegalArgumentException("Delta length (" + pointer - + ") smaller than source text length (" + text1.length() + ")."); - } - return diffs; - } - - - // MATCH FUNCTIONS - - - /** - * Locate the best instance of 'pattern' in 'text' near 'loc'. - * Returns -1 if no match found. - * @param text The text to search. - * @param pattern The pattern to search for. - * @param loc The location to search around. - * @return Best match index or -1. - */ - public int match_main(String text, String pattern, int loc) { - loc = Math.max(0, Math.min(loc, text.length())); - if (text.equals(pattern)) { - // Shortcut (potentially not guaranteed by the algorithm) - return 0; - } else if (text.length() == 0) { - // Nothing to match. - return -1; - } else if (loc + pattern.length() <= text.length() - && text.substring(loc, loc + pattern.length()).equals(pattern)) { - // Perfect match at the perfect spot! (Includes case of null pattern) - return loc; - } else { - // Do a fuzzy compare. - return match_bitap(text, pattern, loc); - } - } - - - /** - * Locate the best instance of 'pattern' in 'text' near 'loc' using the - * Bitap algorithm. Returns -1 if no match found. - * @param text The text to search. - * @param pattern The pattern to search for. - * @param loc The location to search around. - * @return Best match index or -1. - */ - protected int match_bitap(String text, String pattern, int loc) { - assert (Match_MaxBits == 0 || pattern.length() <= Match_MaxBits) - : "Pattern too long for this application."; - - // Initialise the alphabet. - Map<Character, Integer> s = match_alphabet(pattern); - - // Highest score beyond which we give up. - double score_threshold = Match_Threshold; - // Is there a nearby exact match? (speedup) - int best_loc = text.indexOf(pattern, loc); - if (best_loc != -1) { - score_threshold = Math.min(match_bitapScore(0, best_loc, loc, pattern), - score_threshold); - // What about in the other direction? (speedup) - best_loc = text.lastIndexOf(pattern, loc + pattern.length()); - if (best_loc != -1) { - score_threshold = Math.min(match_bitapScore(0, best_loc, loc, pattern), - score_threshold); - } - } - - // Initialise the bit arrays. - int matchmask = 1 << (pattern.length() - 1); - best_loc = -1; - - int bin_min, bin_mid; - int bin_max = pattern.length() + text.length(); - // Empty initialization added to appease Java compiler. - int[] last_rd = new int[0]; - for (int d = 0; d < pattern.length(); d++) { - // Scan for the best match; each iteration allows for one more error. - // Run a binary search to determine how far from 'loc' we can stray at - // this error level. - bin_min = 0; - bin_mid = bin_max; - while (bin_min < bin_mid) { - if (match_bitapScore(d, loc + bin_mid, loc, pattern) - <= score_threshold) { - bin_min = bin_mid; - } else { - bin_max = bin_mid; - } - bin_mid = (bin_max - bin_min) / 2 + bin_min; - } - // Use the result from this iteration as the maximum for the next. - bin_max = bin_mid; - int start = Math.max(1, loc - bin_mid + 1); - int finish = Math.min(loc + bin_mid, text.length()) + pattern.length(); - - int[] rd = new int[finish + 2]; - rd[finish + 1] = (1 << d) - 1; - for (int j = finish; j >= start; j--) { - int charMatch; - if (text.length() <= j - 1 || !s.containsKey(text.charAt(j - 1))) { - // Out of range. - charMatch = 0; - } else { - charMatch = s.get(text.charAt(j - 1)); - } - if (d == 0) { - // First pass: exact match. - rd[j] = ((rd[j + 1] << 1) | 1) & charMatch; - } else { - // Subsequent passes: fuzzy match. - rd[j] = ((rd[j + 1] << 1) | 1) & charMatch - | (((last_rd[j + 1] | last_rd[j]) << 1) | 1) | last_rd[j + 1]; - } - if ((rd[j] & matchmask) != 0) { - double score = match_bitapScore(d, j - 1, loc, pattern); - // This match will almost certainly be better than any existing - // match. But check anyway. - if (score <= score_threshold) { - // Told you so. - score_threshold = score; - best_loc = j - 1; - if (best_loc > loc) { - // When passing loc, don't exceed our current distance from loc. - start = Math.max(1, 2 * loc - best_loc); - } else { - // Already passed loc, downhill from here on in. - break; - } - } - } - } - if (match_bitapScore(d + 1, loc, loc, pattern) > score_threshold) { - // No hope for a (better) match at greater error levels. - break; - } - last_rd = rd; - } - return best_loc; - } - - - /** - * Compute and return the score for a match with e errors and x location. - * @param e Number of errors in match. - * @param x Location of match. - * @param loc Expected location of match. - * @param pattern Pattern being sought. - * @return Overall score for match (0.0 = good, 1.0 = bad). - */ - private double match_bitapScore(int e, int x, int loc, String pattern) { - float accuracy = (float) e / pattern.length(); - int proximity = Math.abs(loc - x); - if (Match_Distance == 0) { - // Dodge divide by zero error. - return proximity == 0 ? accuracy : 1.0; - } - return accuracy + (proximity / (float) Match_Distance); - } - - - /** - * Initialise the alphabet for the Bitap algorithm. - * @param pattern The text to encode. - * @return Hash of character locations. - */ - protected Map<Character, Integer> match_alphabet(String pattern) { - Map<Character, Integer> s = new HashMap<Character, Integer>(); - char[] char_pattern = pattern.toCharArray(); - for (char c : char_pattern) { - s.put(c, 0); - } - int i = 0; - for (char c : char_pattern) { - s.put(c, s.get(c) | (1 << (pattern.length() - i - 1))); - i++; - } - return s; - } - - - // PATCH FUNCTIONS - - - /** - * Increase the context until it is unique, - * but don't let the pattern expand beyond Match_MaxBits. - * @param patch The patch to grow. - * @param text Source text. - */ - protected void patch_addContext(Patch patch, String text) { - if (text.length() == 0) { - return; - } - String pattern = text.substring(patch.start2, patch.start2 + patch.length1); - int padding = 0; - - // Look for the first and last matches of pattern in text. If two different - // matches are found, increase the pattern length. - while (text.indexOf(pattern) != text.lastIndexOf(pattern) - && pattern.length() < Match_MaxBits - Patch_Margin - Patch_Margin) { - padding += Patch_Margin; - pattern = text.substring(Math.max(0, patch.start2 - padding), - Math.min(text.length(), patch.start2 + patch.length1 + padding)); - } - // Add one chunk for good luck. - padding += Patch_Margin; - - // Add the prefix. - String prefix = text.substring(Math.max(0, patch.start2 - padding), - patch.start2); - if (prefix.length() != 0) { - patch.diffs.addFirst(new Diff(Operation.EQUAL, prefix)); - } - // Add the suffix. - String suffix = text.substring(patch.start2 + patch.length1, - Math.min(text.length(), patch.start2 + patch.length1 + padding)); - if (suffix.length() != 0) { - patch.diffs.addLast(new Diff(Operation.EQUAL, suffix)); - } - - // Roll back the start points. - patch.start1 -= prefix.length(); - patch.start2 -= prefix.length(); - // Extend the lengths. - patch.length1 += prefix.length() + suffix.length(); - patch.length2 += prefix.length() + suffix.length(); - } - - - /** - * Compute a list of patches to turn text1 into text2. - * A set of diffs will be computed. - * @param text1 Old text. - * @param text2 New text. - * @return LinkedList of Patch objects. - */ - public LinkedList<Patch> patch_make(String text1, String text2) { - // No diffs provided, compute our own. - LinkedList<Diff> diffs = diff_main(text1, text2, true); - if (diffs.size() > 2) { - diff_cleanupSemantic(diffs); - diff_cleanupEfficiency(diffs); - } - return patch_make(text1, diffs); - } - - - /** - * Compute a list of patches to turn text1 into text2. - * text1 will be derived from the provided diffs. - * @param diffs Array of diff tuples for text1 to text2. - * @return LinkedList of Patch objects. - */ - public LinkedList<Patch> patch_make(LinkedList<Diff> diffs) { - // No origin string provided, compute our own. - String text1 = diff_text1(diffs); - return patch_make(text1, diffs); - } - - - /** - * Compute a list of patches to turn text1 into text2. - * text2 is ignored, diffs are the delta between text1 and text2. - * @param text1 Old text - * @param text2 Ignored. - * @param diffs Array of diff tuples for text1 to text2. - * @return LinkedList of Patch objects. - * @deprecated Prefer patch_make(String text1, LinkedList<Diff> diffs). - */ - public LinkedList<Patch> patch_make(String text1, String text2, - LinkedList<Diff> diffs) { - return patch_make(text1, diffs); - } - - - /** - * Compute a list of patches to turn text1 into text2. - * A set of diffs will be computed. - * @param text1 Old text. - * @param text2 New text. - * @return String representation of a LinkedList of Patch objects. - */ - public String custom_patch_make(String text1, String text2) { - return patch_toText(patch_make(text1, text2)); - } - - - /** - * Compute a list of patches to turn text1 into text2. - * text2 is not provided, diffs are the delta between text1 and text2. - * @param text1 Old text. - * @param diffs Array of diff tuples for text1 to text2. - * @return LinkedList of Patch objects. - */ - public LinkedList<Patch> patch_make(String text1, LinkedList<Diff> diffs) { - LinkedList<Patch> patches = new LinkedList<Patch>(); - if (diffs.isEmpty()) { - return patches; // Get rid of the null case. - } - Patch patch = new Patch(); - int char_count1 = 0; // Number of characters into the text1 string. - int char_count2 = 0; // Number of characters into the text2 string. - // Start with text1 (prepatch_text) and apply the diffs until we arrive at - // text2 (postpatch_text). We recreate the patches one by one to determine - // context info. - String prepatch_text = text1; - String postpatch_text = text1; - for (Diff aDiff : diffs) { - if (patch.diffs.isEmpty() && aDiff.operation != Operation.EQUAL) { - // A new patch starts here. - patch.start1 = char_count1; - patch.start2 = char_count2; - } - - switch (aDiff.operation) { - case INSERT: - patch.diffs.add(aDiff); - patch.length2 += aDiff.text.length(); - postpatch_text = postpatch_text.substring(0, char_count2) - + aDiff.text + postpatch_text.substring(char_count2); - break; - case DELETE: - patch.length1 += aDiff.text.length(); - patch.diffs.add(aDiff); - postpatch_text = postpatch_text.substring(0, char_count2) - + postpatch_text.substring(char_count2 + aDiff.text.length()); - break; - case EQUAL: - if (aDiff.text.length() <= 2 * Patch_Margin - && !patch.diffs.isEmpty() && aDiff != diffs.getLast()) { - // Small equality inside a patch. - patch.diffs.add(aDiff); - patch.length1 += aDiff.text.length(); - patch.length2 += aDiff.text.length(); - } - - if (aDiff.text.length() >= 2 * Patch_Margin) { - // Time for a new patch. - if (!patch.diffs.isEmpty()) { - patch_addContext(patch, prepatch_text); - patches.add(patch); - patch = new Patch(); - // Unlike Unidiff, our patch lists have a rolling context. - // http://code.google.com/p/google-diff-match-patch/wiki/Unidiff - // Update prepatch text & pos to reflect the application of the - // just completed patch. - prepatch_text = postpatch_text; - char_count1 = char_count2; - } - } - break; - } - - // Update the current character count. - if (aDiff.operation != Operation.INSERT) { - char_count1 += aDiff.text.length(); - } - if (aDiff.operation != Operation.DELETE) { - char_count2 += aDiff.text.length(); - } - } - // Pick up the leftover patch if not empty. - if (!patch.diffs.isEmpty()) { - patch_addContext(patch, prepatch_text); - patches.add(patch); - } - - return patches; - } - - - /** - * Given an array of patches, return another array that is identical. - * @param patches Array of patch objects. - * @return Array of patch objects. - */ - public LinkedList<Patch> patch_deepCopy(LinkedList<Patch> patches) { - LinkedList<Patch> patchesCopy = new LinkedList<Patch>(); - for (Patch aPatch : patches) { - Patch patchCopy = new Patch(); - for (Diff aDiff : aPatch.diffs) { - Diff diffCopy = new Diff(aDiff.operation, aDiff.text); - patchCopy.diffs.add(diffCopy); - } - patchCopy.start1 = aPatch.start1; - patchCopy.start2 = aPatch.start2; - patchCopy.length1 = aPatch.length1; - patchCopy.length2 = aPatch.length2; - patchesCopy.add(patchCopy); - } - return patchesCopy; - } - - - /** - * Merge a set of patches onto the text. Return a patched text, as well - * as an array of true/false values indicating which patches were applied. - * @param patches Array of patch objects - * @param text Old text. - * @return Two element Object array, containing the new text and an array of - * boolean values. - */ - public Object[] patch_apply(LinkedList<Patch> patches, String text) { - if (patches.isEmpty()) { - return new Object[]{text, new boolean[0]}; - } - - // Deep copy the patches so that no changes are made to originals. - patches = patch_deepCopy(patches); - - String nullPadding = patch_addPadding(patches); - text = nullPadding + text + nullPadding; - patch_splitMax(patches); - - int x = 0; - // delta keeps track of the offset between the expected and actual location - // of the previous patch. If there are patches expected at positions 10 and - // 20, but the first patch was found at 12, delta is 2 and the second patch - // has an effective expected position of 22. - int delta = 0; - boolean[] results = new boolean[patches.size()]; - for (Patch aPatch : patches) { - int expected_loc = aPatch.start2 + delta; - String text1 = diff_text1(aPatch.diffs); - int start_loc; - int end_loc = -1; - if (text1.length() > this.Match_MaxBits) { - // patch_splitMax will only provide an oversized pattern in the case of - // a monster delete. - start_loc = match_main(text, - text1.substring(0, this.Match_MaxBits), expected_loc); - if (start_loc != -1) { - end_loc = match_main(text, - text1.substring(text1.length() - this.Match_MaxBits), - expected_loc + text1.length() - this.Match_MaxBits); - if (end_loc == -1 || start_loc >= end_loc) { - // Can't find valid trailing context. Drop this patch. - start_loc = -1; - } - } - } else { - start_loc = match_main(text, text1, expected_loc); - } - if (start_loc == -1) { - // No match found. :( - results[x] = false; - // Subtract the delta for this failed patch from subsequent patches. - delta -= aPatch.length2 - aPatch.length1; - } else { - // Found a match. :) - results[x] = true; - delta = start_loc - expected_loc; - String text2; - if (end_loc == -1) { - text2 = text.substring(start_loc, - Math.min(start_loc + text1.length(), text.length())); - } else { - text2 = text.substring(start_loc, - Math.min(end_loc + this.Match_MaxBits, text.length())); - } - if (text1.equals(text2)) { - // Perfect match, just shove the replacement text in. - text = text.substring(0, start_loc) + diff_text2(aPatch.diffs) - + text.substring(start_loc + text1.length()); - } else { - // Imperfect match. Run a diff to get a framework of equivalent - // indices. - LinkedList<Diff> diffs = diff_main(text1, text2, false); - if (text1.length() > this.Match_MaxBits - && diff_levenshtein(diffs) / (float) text1.length() - > this.Patch_DeleteThreshold) { - // The end points match, but the content is unacceptably bad. - results[x] = false; - } else { - diff_cleanupSemanticLossless(diffs); - int index1 = 0; - for (Diff aDiff : aPatch.diffs) { - if (aDiff.operation != Operation.EQUAL) { - int index2 = diff_xIndex(diffs, index1); - if (aDiff.operation == Operation.INSERT) { - // Insertion - text = text.substring(0, start_loc + index2) + aDiff.text - + text.substring(start_loc + index2); - } else if (aDiff.operation == Operation.DELETE) { - // Deletion - text = text.substring(0, start_loc + index2) - + text.substring(start_loc + diff_xIndex(diffs, - index1 + aDiff.text.length())); - } - } - if (aDiff.operation != Operation.DELETE) { - index1 += aDiff.text.length(); - } - } - } - } - } - x++; - } - // Strip the padding off. - text = text.substring(nullPadding.length(), text.length() - - nullPadding.length()); - return new Object[]{text, results}; - } - - - /** - * Add some padding on text start and end so that edges can match something. - * Intended to be called only from within patch_apply. - * @param patches Array of patch objects. - * @return The padding string added to each side. - */ - public String patch_addPadding(LinkedList<Patch> patches) { - int paddingLength = this.Patch_Margin; - String nullPadding = ""; - for (int x = 1; x <= paddingLength; x++) { - nullPadding += String.valueOf((char) x); - } - - // Bump all the patches forward. - for (Patch aPatch : patches) { - aPatch.start1 += paddingLength; - aPatch.start2 += paddingLength; - } - - // Add some padding on start of first diff. - Patch patch = patches.getFirst(); - LinkedList<Diff> diffs = patch.diffs; - if (diffs.isEmpty() || diffs.getFirst().operation != Operation.EQUAL) { - // Add nullPadding equality. - diffs.addFirst(new Diff(Operation.EQUAL, nullPadding)); - patch.start1 -= paddingLength; // Should be 0. - patch.start2 -= paddingLength; // Should be 0. - patch.length1 += paddingLength; - patch.length2 += paddingLength; - } else if (paddingLength > diffs.getFirst().text.length()) { - // Grow first equality. - Diff firstDiff = diffs.getFirst(); - int extraLength = paddingLength - firstDiff.text.length(); - firstDiff.text = nullPadding.substring(firstDiff.text.length()) - + firstDiff.text; - patch.start1 -= extraLength; - patch.start2 -= extraLength; - patch.length1 += extraLength; - patch.length2 += extraLength; - } - - // Add some padding on end of last diff. - patch = patches.getLast(); - diffs = patch.diffs; - if (diffs.isEmpty() || diffs.getLast().operation != Operation.EQUAL) { - // Add nullPadding equality. - diffs.addLast(new Diff(Operation.EQUAL, nullPadding)); - patch.length1 += paddingLength; - patch.length2 += paddingLength; - } else if (paddingLength > diffs.getLast().text.length()) { - // Grow last equality. - Diff lastDiff = diffs.getLast(); - int extraLength = paddingLength - lastDiff.text.length(); - lastDiff.text += nullPadding.substring(0, extraLength); - patch.length1 += extraLength; - patch.length2 += extraLength; - } - - return nullPadding; - } - - - /** - * Look through the patches and break up any which are longer than the - * maximum limit of the match algorithm. - * @param patches LinkedList of Patch objects. - */ - public void patch_splitMax(LinkedList<Patch> patches) { - int patch_size; - String precontext, postcontext; - Patch patch; - int start1, start2; - boolean empty; - Operation diff_type; - String diff_text; - ListIterator<Patch> pointer = patches.listIterator(); - Patch bigpatch = pointer.hasNext() ? pointer.next() : null; - while (bigpatch != null) { - if (bigpatch.length1 <= Match_MaxBits) { - bigpatch = pointer.hasNext() ? pointer.next() : null; - continue; - } - // Remove the big old patch. - pointer.remove(); - patch_size = Match_MaxBits; - start1 = bigpatch.start1; - start2 = bigpatch.start2; - precontext = ""; - while (!bigpatch.diffs.isEmpty()) { - // Create one of several smaller patches. - patch = new Patch(); - empty = true; - patch.start1 = start1 - precontext.length(); - patch.start2 = start2 - precontext.length(); - if (precontext.length() != 0) { - patch.length1 = patch.length2 = precontext.length(); - patch.diffs.add(new Diff(Operation.EQUAL, precontext)); - } - while (!bigpatch.diffs.isEmpty() - && patch.length1 < patch_size - Patch_Margin) { - diff_type = bigpatch.diffs.getFirst().operation; - diff_text = bigpatch.diffs.getFirst().text; - if (diff_type == Operation.INSERT) { - // Insertions are harmless. - patch.length2 += diff_text.length(); - start2 += diff_text.length(); - patch.diffs.addLast(bigpatch.diffs.removeFirst()); - empty = false; - } else if (diff_type == Operation.DELETE && patch.diffs.size() == 1 - && patch.diffs.getFirst().operation == Operation.EQUAL - && diff_text.length() > 2 * patch_size) { - // This is a large deletion. Let it pass in one chunk. - patch.length1 += diff_text.length(); - start1 += diff_text.length(); - empty = false; - patch.diffs.add(new Diff(diff_type, diff_text)); - bigpatch.diffs.removeFirst(); - } else { - // Deletion or equality. Only take as much as we can stomach. - diff_text = diff_text.substring(0, Math.min(diff_text.length(), - patch_size - patch.length1 - Patch_Margin)); - patch.length1 += diff_text.length(); - start1 += diff_text.length(); - if (diff_type == Operation.EQUAL) { - patch.length2 += diff_text.length(); - start2 += diff_text.length(); - } else { - empty = false; - } - patch.diffs.add(new Diff(diff_type, diff_text)); - if (diff_text.equals(bigpatch.diffs.getFirst().text)) { - bigpatch.diffs.removeFirst(); - } else { - bigpatch.diffs.getFirst().text = bigpatch.diffs.getFirst().text - .substring(diff_text.length()); - } - } - } - // Compute the head context for the next patch. - precontext = diff_text2(patch.diffs); - precontext = precontext.substring(Math.max(0, precontext.length() - - Patch_Margin)); - // Append the end context for this patch. - if (diff_text1(bigpatch.diffs).length() > Patch_Margin) { - postcontext = diff_text1(bigpatch.diffs).substring(0, Patch_Margin); - } else { - postcontext = diff_text1(bigpatch.diffs); - } - if (postcontext.length() != 0) { - patch.length1 += postcontext.length(); - patch.length2 += postcontext.length(); - if (!patch.diffs.isEmpty() - && patch.diffs.getLast().operation == Operation.EQUAL) { - patch.diffs.getLast().text += postcontext; - } else { - patch.diffs.add(new Diff(Operation.EQUAL, postcontext)); - } - } - if (!empty) { - pointer.add(patch); - } - } - bigpatch = pointer.hasNext() ? pointer.next() : null; - } - } - - - /** - * Take a list of patches and return a textual representation. - * @param patches List of Patch objects. - * @return Text representation of patches. - */ - public String patch_toText(List<Patch> patches) { - StringBuilder text = new StringBuilder(); - for (Patch aPatch : patches) { - text.append(aPatch); - } - return text.toString(); - } - - - /** - * Parse a textual representation of patches and return a List of Patch - * objects. - * @param textline Text representation of patches. - * @return List of Patch objects. - * @throws IllegalArgumentException If invalid input. - */ - public LinkedList<Patch> patch_fromText(String textline) - throws IllegalArgumentException { - LinkedList<Patch> patches = new LinkedList<Patch>(); - if (textline.length() == 0) { - return patches; - } - List<String> textList = Arrays.asList(textline.split("\n")); - LinkedList<String> text = new LinkedList<String>(textList); - Patch patch; - Pattern patchHeader - = Pattern.compile("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$"); - Matcher m; - char sign; - String line; - while (!text.isEmpty()) { - m = patchHeader.matcher(text.getFirst()); - if (!m.matches()) { - throw new IllegalArgumentException( - "Invalid patch string: " + text.getFirst()); - } - patch = new Patch(); - patches.add(patch); - patch.start1 = Integer.parseInt(m.group(1)); - if (m.group(2).length() == 0) { - patch.start1--; - patch.length1 = 1; - } else if (m.group(2).equals("0")) { - patch.length1 = 0; - } else { - patch.start1--; - patch.length1 = Integer.parseInt(m.group(2)); - } - - patch.start2 = Integer.parseInt(m.group(3)); - if (m.group(4).length() == 0) { - patch.start2--; - patch.length2 = 1; - } else if (m.group(4).equals("0")) { - patch.length2 = 0; - } else { - patch.start2--; - patch.length2 = Integer.parseInt(m.group(4)); - } - text.removeFirst(); - - while (!text.isEmpty()) { - try { - sign = text.getFirst().charAt(0); - } catch (IndexOutOfBoundsException e) { - // Blank line? Whatever. - text.removeFirst(); - continue; - } - line = text.getFirst().substring(1); - line = line.replace("+", "%2B"); // decode would change all "+" to " " - try { - line = URLDecoder.decode(line, "UTF-8"); - } catch (UnsupportedEncodingException e) { - // Not likely on modern system. - throw new Error("This system does not support UTF-8.", e); - } catch (IllegalArgumentException e) { - // Malformed URI sequence. - throw new IllegalArgumentException( - "Illegal escape in patch_fromText: " + line, e); - } - if (sign == '-') { - // Deletion. - patch.diffs.add(new Diff(Operation.DELETE, line)); - } else if (sign == '+') { - // Insertion. - patch.diffs.add(new Diff(Operation.INSERT, line)); - } else if (sign == ' ') { - // Minor equality. - patch.diffs.add(new Diff(Operation.EQUAL, line)); - } else if (sign == '@') { - // Start of next patch. - break; - } else { - // WTF? - throw new IllegalArgumentException( - "Invalid patch mode '" + sign + "' in: " + line); - } - text.removeFirst(); - } - } - return patches; - } - - - /** - * Class representing one diff operation. - */ - public static class Diff { - /** - * One of: INSERT, DELETE or EQUAL. - */ - public Operation operation; - /** - * The text associated with this diff operation. - */ - public String text; - - /** - * Constructor. Initializes the diff with the provided values. - * @param operation One of INSERT, DELETE or EQUAL. - * @param text The text being applied. - */ - public Diff(Operation operation, String text) { - // Construct a diff with the specified operation and text. - this.operation = operation; - this.text = text; - } - - - /** - * Display a human-readable version of this Diff. - * @return text version. - */ - public String toString() { - String prettyText = this.text.replace('\n', '\u00b6'); - return "Diff(" + this.operation + ",\"" + prettyText + "\")"; - } - - - /** - * Is this Diff equivalent to another Diff? - * @param d Another Diff to compare against. - * @return true or false. - */ - public boolean equals(Object d) { - try { - return (((Diff) d).operation == this.operation) - && (((Diff) d).text.equals(this.text)); - } catch (ClassCastException e) { - return false; - } - } - } - - - /** - * Class representing one patch operation. - */ - public static class Patch { - public LinkedList<Diff> diffs; - public int start1; - public int start2; - public int length1; - public int length2; - - - /** - * Constructor. Initializes with an empty list of diffs. - */ - public Patch() { - this.diffs = new LinkedList<Diff>(); - } - - - /** - * Emmulate GNU diff's format. - * Header: @@ -382,8 +481,9 @@ - * Indicies are printed as 1-based, not 0-based. - * @return The GNU diff string. - */ - public String toString() { - String coords1, coords2; - if (this.length1 == 0) { - coords1 = this.start1 + ",0"; - } else if (this.length1 == 1) { - coords1 = Integer.toString(this.start1 + 1); - } else { - coords1 = (this.start1 + 1) + "," + this.length1; - } - if (this.length2 == 0) { - coords2 = this.start2 + ",0"; - } else if (this.length2 == 1) { - coords2 = Integer.toString(this.start2 + 1); - } else { - coords2 = (this.start2 + 1) + "," + this.length2; - } - StringBuilder text = new StringBuilder(); - text.append("@@ -").append(coords1).append(" +").append(coords2) - .append(" @@\n"); - // Escape the body of the patch with %xx notation. - for (Diff aDiff : this.diffs) { - switch (aDiff.operation) { - case INSERT: - text.append('+'); - break; - case DELETE: - text.append('-'); - break; - case EQUAL: - text.append(' '); - break; - } - try { - text.append(URLEncoder.encode(aDiff.text, "UTF-8").replace('+', ' ')) - .append("\n"); - } catch (UnsupportedEncodingException e) { - // Not likely on modern system. - throw new Error("This system does not support UTF-8.", e); - } - } - return unescapeForEncodeUriCompatability(text.toString()); - } - } - - - /** - * Unescape selected chars for compatability with JavaScript's encodeURI. - * In speed critical applications this could be dropped since the - * receiving application will certainly decode these fine. - * Note that this function is case-sensitive. Thus "%3f" would not be - * unescaped. But this is ok because it is only called with the output of - * URLEncoder.encode which returns uppercase hex. - * - * Example: "%3F" -> "?", "%24" -> "$", etc. - * - * @param str The string to escape. - * @return The escaped string. - */ - private static String unescapeForEncodeUriCompatability(String str) { - return str.replace("%21", "!").replace("%7E", "~") - .replace("%27", "'").replace("%28", "(").replace("%29", ")") - .replace("%3B", ";").replace("%2F", "/").replace("%3F", "?") - .replace("%3A", ":").replace("%40", "@").replace("%26", "&") - .replace("%3D", "=").replace("%2B", "+").replace("%24", "$") - .replace("%2C", ",").replace("%23", "#"); - } -} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala index 981b75c0fee5582b1f1c0e42826489e48685f284..3014c51d23090c0f0428bf34bf776c59032b6fa1 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala @@ -36,9 +36,6 @@ trait SystemConfiguration { lazy val toAkkaAppsJsonChannel = Try(config.getString("eventBus.toAkkaAppsChannel")).getOrElse("to-akka-apps-json-channel") lazy val fromAkkaAppsJsonChannel = Try(config.getString("eventBus.fromAkkaAppsChannel")).getOrElse("from-akka-apps-json-channel") - lazy val maxNumberOfNotes = Try(config.getInt("sharedNotes.maxNumberOfNotes")).getOrElse(3) - lazy val maxNumberOfUndos = Try(config.getInt("sharedNotes.maxNumberOfUndos")).getOrElse(30) - lazy val applyPermissionCheck = Try(config.getBoolean("apps.checkPermissions")).getOrElse(false) lazy val voiceConfRecordPath = Try(config.getString("voiceConf.recordPath")).getOrElse("/var/freeswitch/meetings") diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/SharedNotesModel.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/SharedNotesModel.scala deleted file mode 100755 index cb449550276f689853a3e28e333f0f3a77d3c9a0..0000000000000000000000000000000000000000 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/SharedNotesModel.scala +++ /dev/null @@ -1,126 +0,0 @@ -package org.bigbluebutton.core.apps - -import name.fraser.neil.plaintext.diff_match_patch - -import scala.collection._ -import scala.collection.immutable.List -import scala.collection.mutable.HashMap - -import org.bigbluebutton.SystemConfiguration -import org.bigbluebutton.common2.msgs.Note -import org.bigbluebutton.common2.msgs.NoteReport -import org.bigbluebutton.core.models.SystemUser - -class SharedNotesModel extends SystemConfiguration { - val MAIN_NOTE_ID = "MAIN_NOTE" - val SYSTEM_ID = SystemUser.ID - - private val patcher = new diff_match_patch() - - private var notesCounter = 0; - private var removedNotes: Set[Int] = Set() - - val notes = new HashMap[String, Note]() - notes += (MAIN_NOTE_ID -> new Note("", "", 0, List[(String, String)](), List[(String, String)]())) - - def patchNote(noteId: String, patch: String, operation: String): (Integer, String, Boolean, Boolean) = { - val note = notes(noteId) - val document = note.document - var undoPatches = note.undoPatches - var redoPatches = note.redoPatches - - val patchToApply = operation match { - case "PATCH" => { - patch - } - case "UNDO" => { - if (undoPatches.isEmpty) { - return (-1, "", false, false) - } else { - val (undo, redo) = undoPatches.head - undoPatches = undoPatches.tail - redoPatches = (undo, redo) :: redoPatches - undo - } - } - case "REDO" => { - if (redoPatches.isEmpty) { - return (-1, "", false, false) - } else { - val (undo, redo) = redoPatches.head - redoPatches = redoPatches.tail - undoPatches = (undo, redo) :: undoPatches - redo - } - } - } - - val patchObjects = patcher.patch_fromText(patchToApply) - val result = patcher.patch_apply(patchObjects, document) - - // If it is a patch operation, save an undo patch and clear redo stack - if (operation == "PATCH") { - undoPatches = (patcher.custom_patch_make(result(0).toString(), document), patchToApply) :: undoPatches - redoPatches = List[(String, String)]() - - if (undoPatches.size > maxNumberOfUndos) { - undoPatches = undoPatches.dropRight(1) - } - } - - val patchCounter = note.patchCounter + 1 - notes(noteId) = new Note(note.name, result(0).toString(), patchCounter, undoPatches, redoPatches) - (patchCounter, patchToApply, !undoPatches.isEmpty, !redoPatches.isEmpty) - } - - def clearNote(noteId: String): Option[NoteReport] = { - val note = notes(noteId) - val patchCounter = note.patchCounter + 1 - notes(noteId) = new Note(note.name, "", patchCounter, List[(String, String)](), List[(String, String)]()) - getNoteReport(noteId) - } - - def createNote(noteName: String = ""): (String, Boolean) = { - var noteId = 0 - if (removedNotes.isEmpty) { - notesCounter += 1 - noteId = notesCounter - } else { - noteId = removedNotes.min - removedNotes -= noteId - } - notes += (noteId.toString -> new Note(noteName, "", 0, List[(String, String)](), List[(String, String)]())) - - (noteId.toString, isNotesLimit) - } - - def destroyNote(noteId: String): Boolean = { - removedNotes += noteId.toInt - notes -= noteId - isNotesLimit - } - - def notesReport: HashMap[String, NoteReport] = { - val report = new HashMap[String, NoteReport]() - notes foreach { - case (id, note) => - report += (id -> noteToReport(note)) - } - report - } - - def getNoteReport(noteId: String): Option[NoteReport] = { - notes.get(noteId) match { - case Some(note) => Some(noteToReport(note)) - case None => None - } - } - - def isNotesLimit: Boolean = { - notes.size >= maxNumberOfNotes - } - - private def noteToReport(note: Note): NoteReport = { - new NoteReport(note.name, note.document, note.patchCounter, !note.undoPatches.isEmpty, !note.redoPatches.isEmpty) - } -} \ No newline at end of file diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/ClearSharedNotePubMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/ClearSharedNotePubMsgHdlr.scala deleted file mode 100755 index 018d497cd4cfa0326854bc26e6b62028742df78a..0000000000000000000000000000000000000000 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/ClearSharedNotePubMsgHdlr.scala +++ /dev/null @@ -1,35 +0,0 @@ -package org.bigbluebutton.core.apps.sharednotes - -import org.bigbluebutton.common2.msgs._ -import org.bigbluebutton.core.bus.MessageBus -import org.bigbluebutton.core.running.LiveMeeting -import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait } - -trait ClearSharedNotePubMsgHdlr extends RightsManagementTrait { - this: SharedNotesApp2x => - - def handle(msg: ClearSharedNotePubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = { - - def broadcastEvent(msg: ClearSharedNotePubMsg, noteReport: NoteReport): Unit = { - val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId) - val envelope = BbbCoreEnvelope(SyncSharedNoteEvtMsg.NAME, routing) - val header = BbbClientMsgHeader(SyncSharedNoteEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId) - - val body = SyncSharedNoteEvtMsgBody(msg.body.noteId, noteReport) - val event = SyncSharedNoteEvtMsg(header, body) - val msgEvent = BbbCommonEnvCoreMsg(envelope, event) - bus.outGW.send(msgEvent) - } - - if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) { - val meetingId = liveMeeting.props.meetingProp.intId - val reason = "No permission to clear shared notes in meeting." - PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting) - } else { - liveMeeting.notesModel.clearNote(msg.body.noteId) match { - case Some(noteReport) => broadcastEvent(msg, noteReport) - case None => log.warning("Could not find note " + msg.body.noteId) - } - } - } -} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/CreateSharedNoteReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/CreateSharedNoteReqMsgHdlr.scala deleted file mode 100755 index 0e778141f25e24e43c67858b7f17419a0a9235c1..0000000000000000000000000000000000000000 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/CreateSharedNoteReqMsgHdlr.scala +++ /dev/null @@ -1,35 +0,0 @@ -package org.bigbluebutton.core.apps.sharednotes - -import org.bigbluebutton.common2.msgs._ -import org.bigbluebutton.core.bus.MessageBus -import org.bigbluebutton.core.running.LiveMeeting -import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait } - -trait CreateSharedNoteReqMsgHdlr extends RightsManagementTrait { - this: SharedNotesApp2x => - - def handle(msg: CreateSharedNoteReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = { - - def broadcastEvent(msg: CreateSharedNoteReqMsg, noteId: String, isNotesLimit: Boolean): Unit = { - val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId) - val envelope = BbbCoreEnvelope(CreateSharedNoteRespMsg.NAME, routing) - val header = BbbClientMsgHeader(CreateSharedNoteRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId) - - val body = CreateSharedNoteRespMsgBody(noteId, msg.body.noteName, isNotesLimit) - val event = CreateSharedNoteRespMsg(header, body) - val msgEvent = BbbCommonEnvCoreMsg(envelope, event) - bus.outGW.send(msgEvent) - } - - if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) { - val meetingId = liveMeeting.props.meetingProp.intId - val reason = "No permission to create new shared note." - PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting) - } else { - if (!liveMeeting.notesModel.isNotesLimit) { - val (noteId, isNotesLimit) = liveMeeting.notesModel.createNote(msg.body.noteName) - broadcastEvent(msg, noteId, isNotesLimit) - } - } - } -} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/DestroySharedNoteReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/DestroySharedNoteReqMsgHdlr.scala deleted file mode 100755 index 73dbc6488f106aaf9c3fbd0a6344c9644e778af7..0000000000000000000000000000000000000000 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/DestroySharedNoteReqMsgHdlr.scala +++ /dev/null @@ -1,33 +0,0 @@ -package org.bigbluebutton.core.apps.sharednotes - -import org.bigbluebutton.common2.msgs._ -import org.bigbluebutton.core.bus.MessageBus -import org.bigbluebutton.core.running.LiveMeeting -import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait } - -trait DestroySharedNoteReqMsgHdlr extends RightsManagementTrait { - this: SharedNotesApp2x => - - def handle(msg: DestroySharedNoteReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = { - - def broadcastEvent(msg: DestroySharedNoteReqMsg, isNotesLimit: Boolean): Unit = { - val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId) - val envelope = BbbCoreEnvelope(DestroySharedNoteRespMsg.NAME, routing) - val header = BbbClientMsgHeader(DestroySharedNoteRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId) - - val body = DestroySharedNoteRespMsgBody(msg.body.noteId, isNotesLimit) - val event = DestroySharedNoteRespMsg(header, body) - val msgEvent = BbbCommonEnvCoreMsg(envelope, event) - bus.outGW.send(msgEvent) - } - - if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) { - val meetingId = liveMeeting.props.meetingProp.intId - val reason = "No permission to destory shared note." - PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting) - } else { - val isNotesLimit = liveMeeting.notesModel.destroyNote(msg.body.noteId) - broadcastEvent(msg, isNotesLimit) - } - } -} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/GetSharedNotesPubMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/GetSharedNotesPubMsgHdlr.scala deleted file mode 100755 index 807ddc3fce9733ab80babccea52dc04f61076ace..0000000000000000000000000000000000000000 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/GetSharedNotesPubMsgHdlr.scala +++ /dev/null @@ -1,27 +0,0 @@ -package org.bigbluebutton.core.apps.sharednotes - -import org.bigbluebutton.common2.msgs._ -import org.bigbluebutton.core.bus.MessageBus -import org.bigbluebutton.core.running.{ LiveMeeting } - -trait GetSharedNotesPubMsgHdlr { - this: SharedNotesApp2x => - - def handle(msg: GetSharedNotesPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = { - - def broadcastEvent(msg: GetSharedNotesPubMsg, notesReport: Map[String, NoteReport], isNotesLimit: Boolean): Unit = { - val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, msg.header.userId) - val envelope = BbbCoreEnvelope(GetSharedNotesEvtMsg.NAME, routing) - val header = BbbClientMsgHeader(GetSharedNotesEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId) - - val body = GetSharedNotesEvtMsgBody(notesReport, isNotesLimit) - val event = GetSharedNotesEvtMsg(header, body) - val msgEvent = BbbCommonEnvCoreMsg(envelope, event) - bus.outGW.send(msgEvent) - } - - val isNotesLimit = liveMeeting.notesModel.isNotesLimit - val notesReport = liveMeeting.notesModel.notesReport.toMap - broadcastEvent(msg, notesReport, isNotesLimit) - } -} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/SharedNotesApp2x.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/SharedNotesApp2x.scala deleted file mode 100755 index c37dbcb8e06b61673c11146dbc0405afd8969b3e..0000000000000000000000000000000000000000 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/SharedNotesApp2x.scala +++ /dev/null @@ -1,15 +0,0 @@ -package org.bigbluebutton.core.apps.sharednotes - -import akka.actor.ActorContext -import akka.event.Logging - -class SharedNotesApp2x(implicit val context: ActorContext) - extends GetSharedNotesPubMsgHdlr - with SyncSharedNotePubMsgHdlr - with ClearSharedNotePubMsgHdlr - with UpdateSharedNoteReqMsgHdlr - with CreateSharedNoteReqMsgHdlr - with DestroySharedNoteReqMsgHdlr { - - val log = Logging(context.system, getClass) -} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/SyncSharedNotePubMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/SyncSharedNotePubMsgHdlr.scala deleted file mode 100755 index c278db65a0467717a352d7367ef63838cc88a084..0000000000000000000000000000000000000000 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/SyncSharedNotePubMsgHdlr.scala +++ /dev/null @@ -1,28 +0,0 @@ -package org.bigbluebutton.core.apps.sharednotes - -import org.bigbluebutton.common2.msgs._ -import org.bigbluebutton.core.bus.MessageBus -import org.bigbluebutton.core.running.{ LiveMeeting } - -trait SyncSharedNotePubMsgHdlr { - this: SharedNotesApp2x => - - def handle(msg: SyncSharedNotePubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = { - - def broadcastEvent(msg: SyncSharedNotePubMsg, noteReport: NoteReport): Unit = { - val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, msg.header.userId) - val envelope = BbbCoreEnvelope(SyncSharedNoteEvtMsg.NAME, routing) - val header = BbbClientMsgHeader(SyncSharedNoteEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId) - - val body = SyncSharedNoteEvtMsgBody(msg.body.noteId, noteReport) - val event = SyncSharedNoteEvtMsg(header, body) - val msgEvent = BbbCommonEnvCoreMsg(envelope, event) - bus.outGW.send(msgEvent) - } - - liveMeeting.notesModel.getNoteReport(msg.body.noteId) match { - case Some(noteReport) => broadcastEvent(msg, noteReport) - case None => log.warning("Could not find note " + msg.body.noteId) - } - } -} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/UpdateSharedNoteReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/UpdateSharedNoteReqMsgHdlr.scala deleted file mode 100755 index 6919d8b87e7aba05d25d8ed6f2e1043c97444071..0000000000000000000000000000000000000000 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/sharednotes/UpdateSharedNoteReqMsgHdlr.scala +++ /dev/null @@ -1,38 +0,0 @@ -package org.bigbluebutton.core.apps.sharednotes - -import org.bigbluebutton.common2.msgs._ -import org.bigbluebutton.core.bus.MessageBus -import org.bigbluebutton.core.running.{ LiveMeeting } - -trait UpdateSharedNoteReqMsgHdlr { - this: SharedNotesApp2x => - - def handle(msg: UpdateSharedNoteReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = { - - def broadcastEvent(msg: UpdateSharedNoteReqMsg, userId: String, patch: String, patchId: Int, undo: Boolean, redo: Boolean): Unit = { - val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, userId) - val envelope = BbbCoreEnvelope(UpdateSharedNoteRespMsg.NAME, routing) - val header = BbbClientMsgHeader(UpdateSharedNoteRespMsg.NAME, liveMeeting.props.meetingProp.intId, userId) - - val body = UpdateSharedNoteRespMsgBody(msg.body.noteId, patch, patchId, undo, redo) - val event = UpdateSharedNoteRespMsg(header, body) - val msgEvent = BbbCommonEnvCoreMsg(envelope, event) - bus.outGW.send(msgEvent) - } - - val userId = msg.body.operation match { - case "PATCH" => msg.header.userId - case "UNDO" => liveMeeting.notesModel.SYSTEM_ID - case "REDO" => liveMeeting.notesModel.SYSTEM_ID - case _ => return - } - - val (patchId, patch, undo, redo) = liveMeeting.notesModel.patchNote(msg.body.noteId, msg.body.patch, msg.body.operation) - - if (patch != "") { - broadcastEvent(msg, userId, patch, patchId, undo, redo) - } else { - log.warning("Could not patch note " + msg.body.noteId) - } - } -} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/MeetingActivityResponseCmdMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/MeetingActivityResponseCmdMsgHdlr.scala deleted file mode 100755 index eca0ac65c35b1665efcf6ecfffa933281da78682..0000000000000000000000000000000000000000 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/MeetingActivityResponseCmdMsgHdlr.scala +++ /dev/null @@ -1,43 +0,0 @@ -package org.bigbluebutton.core.apps.users - -import org.bigbluebutton.common2.domain.DefaultProps -import org.bigbluebutton.common2.msgs._ -import org.bigbluebutton.core.domain.MeetingState2x -import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter } - -trait MeetingActivityResponseCmdMsgHdlr { - this: UsersApp => - - val liveMeeting: LiveMeeting - val outGW: OutMsgRouter - - def handleMeetingActivityResponseCmdMsg( - msg: MeetingActivityResponseCmdMsg, - state: MeetingState2x - ): MeetingState2x = { - processMeetingActivityResponse(liveMeeting.props, outGW, msg) - val tracker = state.inactivityTracker.resetWarningSentAndTimestamp() - state.update(tracker) - } - - def processMeetingActivityResponse( - props: DefaultProps, - outGW: OutMsgRouter, - msg: MeetingActivityResponseCmdMsg - ): Unit = { - - def buildMeetingIsActiveEvtMsg(meetingId: String): BbbCommonEnvCoreMsg = { - val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, "not-used") - val envelope = BbbCoreEnvelope(MeetingIsActiveEvtMsg.NAME, routing) - val body = MeetingIsActiveEvtMsgBody(meetingId) - val header = BbbClientMsgHeader(MeetingIsActiveEvtMsg.NAME, meetingId, "not-used") - val event = MeetingIsActiveEvtMsg(header, body) - - BbbCommonEnvCoreMsg(envelope, event) - } - - val event = buildMeetingIsActiveEvtMsg(props.meetingProp.intId) - outGW.send(event) - - } -} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/RegisterUserReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/RegisterUserReqMsgHdlr.scala index 51b77f4d3f0708cdea7eeb103e7d7d8c48cd6bd7..50d27ad645e7f97959fab2be4b58a9d38ddbdd4f 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/RegisterUserReqMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/RegisterUserReqMsgHdlr.scala @@ -56,7 +56,7 @@ trait RegisterUserReqMsgHdlr { val g = GuestApprovedVO(regUser.id, GuestStatus.ALLOW) UsersApp.approveOrRejectGuest(liveMeeting, outGW, g, SystemUser.ID) case GuestStatus.WAIT => - val guest = GuestWaiting(regUser.id, regUser.name, regUser.role, regUser.guest, regUser.authed) + val guest = GuestWaiting(regUser.id, regUser.name, regUser.role, regUser.guest, regUser.avatarURL, regUser.authed) addGuestToWaitingForApproval(guest, liveMeeting.guestsWaiting) notifyModeratorsOfGuestWaiting(Vector(guest), liveMeeting.users2x, liveMeeting.props.meetingProp.intId) case GuestStatus.DENY => diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala index eec9ff6431b2dde8df320067ec6dca4f9aea0619..dc13f8c106867dae8fe4e552351a0a0a7118d250 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala @@ -140,7 +140,6 @@ class UsersApp( with ChangeUserRoleCmdMsgHdlr with SyncGetUsersMeetingRespMsgHdlr with LogoutAndEndMeetingCmdMsgHdlr - with MeetingActivityResponseCmdMsgHdlr with SetRecordingStatusCmdMsgHdlr with RecordAndClearPreviousMarkersCmdMsgHdlr with SendRecordingTimerInternalMsgHdlr diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingState2x.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingState2x.scala index 5b405145f780ad298929aade560ed5d2ff9a7741..6a4bb41efb28b5c68845a5c83bdd9014d7a83863 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingState2x.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingState2x.scala @@ -12,7 +12,6 @@ case class MeetingState2x( groupChats: GroupChats, presentationPodManager: PresentationPodManager, breakout: Option[BreakoutModel], - inactivityTracker: MeetingInactivityTracker, expiryTracker: MeetingExpiryTracker, recordingTracker: MeetingRecordingTracker ) { @@ -21,13 +20,11 @@ case class MeetingState2x( def update(presPodManager: PresentationPodManager): MeetingState2x = copy(presentationPodManager = presPodManager) def update(breakout: Option[BreakoutModel]): MeetingState2x = copy(breakout = breakout) def update(expiry: MeetingExpiryTracker): MeetingState2x = copy(expiryTracker = expiry) - def update(inactivityTracker: MeetingInactivityTracker): MeetingState2x = copy(inactivityTracker = inactivityTracker) def update(recordingTracker: MeetingRecordingTracker): MeetingState2x = copy(recordingTracker = recordingTracker) } object MeetingEndReason { val ENDED_FROM_API = "ENDED_FROM_API" - val ENDED_DUE_TO_INACTIVITY = "ENDED_DUE_TO_INACTIVITY" val ENDED_WHEN_NOT_JOINED = "ENDED_WHEN_NOT_JOINED" val ENDED_WHEN_LAST_USER_LEFT = "ENDED_WHEN_LAST_USER_LEFT" val ENDED_AFTER_USER_LOGGED_OUT = "ENDED_AFTER_USER_LOGGED_OUT" diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingInactivityTracker.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingTrackers.scala similarity index 71% rename from akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingInactivityTracker.scala rename to akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingTrackers.scala index bdaaa48c72d89cc1e2002ed62faf09cbea34d18c..5b1c906870abc814289ade3d77b4b4213c1d271c 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingInactivityTracker.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/domain/MeetingTrackers.scala @@ -1,43 +1,5 @@ package org.bigbluebutton.core.domain -case class MeetingInactivityTracker( - val maxInactivityTimeoutInMs: Long, - val warningBeforeMaxInMs: Long, - lastActivityTimestampInMs: Long, - warningSent: Boolean, - warningSentOnTimestampInMs: Long -) { - def setWarningSentAndTimestamp(nowInMs: Long): MeetingInactivityTracker = { - copy(warningSent = true, warningSentOnTimestampInMs = nowInMs) - } - - def resetWarningSentAndTimestamp(): MeetingInactivityTracker = { - copy(warningSent = false, warningSentOnTimestampInMs = 0L) - } - - def updateLastActivityTimestamp(nowInMs: Long): MeetingInactivityTracker = { - copy(lastActivityTimestampInMs = nowInMs) - } - - def hasRecentActivity(nowInMs: Long): Boolean = { - val left = nowInMs - lastActivityTimestampInMs - val right = maxInactivityTimeoutInMs - warningBeforeMaxInMs - left < right - } - - def isMeetingInactive(nowInMs: Long): Boolean = { - if (maxInactivityTimeoutInMs > 0) { - warningSent && (nowInMs - lastActivityTimestampInMs) > maxInactivityTimeoutInMs - } else { - false - } - } - - def timeLeftInMs(nowInMs: Long): Long = { - lastActivityTimestampInMs + maxInactivityTimeoutInMs - nowInMs - } -} - case class MeetingExpiryTracker( startedOnInMs: Long, userHasJoined: Boolean, diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/GuestsWaiting.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/GuestsWaiting.scala index e3ad9be5869b7d21f8361d61358698b87b5c4efc..9454db88c319fb63ea7e42a626e75caa269a3526 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/GuestsWaiting.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/GuestsWaiting.scala @@ -51,7 +51,7 @@ class GuestsWaiting { def setGuestPolicy(policy: GuestPolicy) = guestPolicy = policy } -case class GuestWaiting(intId: String, name: String, role: String, guest: Boolean, authenticated: Boolean) +case class GuestWaiting(intId: String, name: String, role: String, guest: Boolean, avatar: String, authenticated: Boolean) case class GuestPolicy(policy: String, setBy: String) object GuestPolicyType { diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Polls.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Polls.scala index 4aab4ac88c3d591b9c074795250ca5491bbfc242..6bec9374607e1feca3773e4489efda00ae77cefd 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Polls.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Polls.scala @@ -13,33 +13,49 @@ object Polls { def handleStartPollReqMsg(state: MeetingState2x, userId: String, pollId: String, pollType: String, lm: LiveMeeting): Option[SimplePollOutVO] = { - def createPoll(pollId: String, numRespondents: Int): Option[Poll] = { + + def createPoll(stampedPollId: String): Option[Poll] = { + val numRespondents: Int = Users2x.numUsers(lm.users2x) - 1 // subtract the presenter + for { - poll <- PollFactory.createPoll(pollId, pollType, numRespondents, None) + poll <- PollFactory.createPoll(stampedPollId, pollType, numRespondents, None) } yield { lm.polls.save(poll) poll } } - for { + val pollWithPresentation = for { pod <- state.presentationPodManager.getDefaultPod() pres <- pod.getCurrentPresentation() page <- PresentationInPod.getCurrentPage(pres) pageId: String = if (pollId.contains("deskshare")) "deskshare" else page.id stampedPollId: String = pageId + "/" + System.currentTimeMillis() - numRespondents: Int = Users2x.numUsers(lm.users2x) - 1 // subtract the presenter - poll <- createPoll(stampedPollId, numRespondents) + poll <- createPoll(stampedPollId) simplePoll <- getSimplePoll(poll.id, lm.polls) } yield { startPoll(simplePoll.id, lm.polls) simplePoll } + + pollWithPresentation match { + case None => { + val stampedPollId: String = "public" + "/" + System.currentTimeMillis() + for { + poll <- createPoll(stampedPollId) + simplePoll <- getSimplePoll(poll.id, lm.polls) + } yield { + startPoll(simplePoll.id, lm.polls) + simplePoll + } + } + case default => default + } } def handleStopPollReqMsg(state: MeetingState2x, userId: String, lm: LiveMeeting): Option[String] = { - for { + var stoppedPoll = for { pod <- state.presentationPodManager.getDefaultPod() pres <- pod.getCurrentPresentation() page <- PresentationInPod.getCurrentPage(pres) @@ -48,6 +64,18 @@ object Polls { stopPoll(curPoll.id, lm.polls) curPoll.id } + + stoppedPoll match { + case None => { + for { + curPoll <- getRunningPollThatStartsWith("public", lm.polls) + } yield { + stopPoll(curPoll.id, lm.polls) + curPoll.id + } + } + case default => default + } } def handleShowPollResultReqMsg(state: MeetingState2x, requesterId: String, pollId: String, lm: LiveMeeting): Option[(SimplePollResultOutVO, AnnotationVO)] = { @@ -131,28 +159,44 @@ object Polls { def handleStartCustomPollReqMsg(state: MeetingState2x, requesterId: String, pollId: String, pollType: String, answers: Seq[String], lm: LiveMeeting): Option[SimplePollOutVO] = { - def createPoll(pollId: String, numRespondents: Int): Option[Poll] = { + def createPoll(stampedPollId: String): Option[Poll] = { + val numRespondents: Int = Users2x.numUsers(lm.users2x) - 1 // subtract the presenter for { - poll <- PollFactory.createPoll(pollId, pollType, numRespondents, Some(answers)) + poll <- PollFactory.createPoll(stampedPollId, pollType, numRespondents, Some(answers)) } yield { lm.polls.save(poll) poll } } - for { + val pollWithPresentation = for { pod <- state.presentationPodManager.getDefaultPod() pres <- pod.getCurrentPresentation() page <- PresentationInPod.getCurrentPage(pres) pageId: String = if (pollId.contains("deskshare")) "deskshare" else page.id stampedPollId: String = pageId + "/" + System.currentTimeMillis() - numRespondents: Int = Users2x.numUsers(lm.users2x) - 1 // subtract the presenter - poll <- createPoll(stampedPollId, numRespondents) - simplePoll <- getSimplePoll(stampedPollId, lm.polls) + + poll <- createPoll(stampedPollId) + simplePoll <- getSimplePoll(poll.id, lm.polls) } yield { - startPoll(poll.id, lm.polls) + startPoll(simplePoll.id, lm.polls) simplePoll } + + pollWithPresentation match { + case None => { + val stampedPollId: String = "public" + "/" + System.currentTimeMillis() + for { + poll <- createPoll(stampedPollId) + simplePoll <- getSimplePoll(poll.id, lm.polls) + } yield { + startPoll(simplePoll.id, lm.polls) + simplePoll + } + } + case default => default + } + } // diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala index 1842b9a24ed18138c39b045629f78ced7484775f..683f07d0b0aabcd85962600150d2e8d9751bb5d4 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala @@ -255,20 +255,6 @@ class ReceivedJsonMsgHandlerActor( case SendCaptionHistoryReqMsg.NAME => routeGenericMsg[SendCaptionHistoryReqMsg](envelope, jsonNode) - // Shared notes - case GetSharedNotesPubMsg.NAME => - routeGenericMsg[GetSharedNotesPubMsg](envelope, jsonNode) - case SyncSharedNotePubMsg.NAME => - routeGenericMsg[SyncSharedNotePubMsg](envelope, jsonNode) - case ClearSharedNotePubMsg.NAME => - routeGenericMsg[ClearSharedNotePubMsg](envelope, jsonNode) - case UpdateSharedNoteReqMsg.NAME => - routeGenericMsg[UpdateSharedNoteReqMsg](envelope, jsonNode) - case CreateSharedNoteReqMsg.NAME => - routeGenericMsg[CreateSharedNoteReqMsg](envelope, jsonNode) - case DestroySharedNoteReqMsg.NAME => - routeGenericMsg[DestroySharedNoteReqMsg](envelope, jsonNode) - // Chat case GetChatHistoryReqMsg.NAME => routeGenericMsg[GetChatHistoryReqMsg](envelope, jsonNode) @@ -284,8 +270,6 @@ class ReceivedJsonMsgHandlerActor( // Meeting case EndMeetingSysCmdMsg.NAME => routeGenericMsg[EndMeetingSysCmdMsg](envelope, jsonNode) - case MeetingActivityResponseCmdMsg.NAME => - routeGenericMsg[MeetingActivityResponseCmdMsg](envelope, jsonNode) case LogoutAndEndMeetingCmdMsg.NAME => routeGenericMsg[LogoutAndEndMeetingCmdMsg](envelope, jsonNode) case SetRecordingStatusCmdMsg.NAME => diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/LiveMeeting.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/LiveMeeting.scala index 31be44636ac04fa831be69bfa9d550fa2c4cef99..706dc2c7470bbf35b92bd1cabe465120126a4132 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/LiveMeeting.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/LiveMeeting.scala @@ -16,7 +16,6 @@ class LiveMeeting( val wbModel: WhiteboardModel, val presModel: PresentationModel, val captionModel: CaptionModel, - val notesModel: SharedNotesModel, val webcams: Webcams, val voiceUsers: VoiceUsers, val users2x: Users2x, diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala index c0a1735a92841210e0d1f19457236444c06383ef..8a1955c2e691dd618ca05957bee90d713673ab52 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala @@ -19,7 +19,6 @@ import org.bigbluebutton.core.apps.chat.ChatApp2x import org.bigbluebutton.core.apps.screenshare.ScreenshareApp2x import org.bigbluebutton.core.apps.presentation.PresentationApp2x import org.bigbluebutton.core.apps.users.UsersApp2x -import org.bigbluebutton.core.apps.sharednotes.SharedNotesApp2x import org.bigbluebutton.core.apps.whiteboard.WhiteboardApp2x import org.bigbluebutton.core.bus._ import org.bigbluebutton.core.models._ @@ -113,7 +112,6 @@ class MeetingActor( val presentationApp2x = new PresentationApp2x val screenshareApp2x = new ScreenshareApp2x val captionApp2x = new CaptionApp2x - val sharedNotesApp2x = new SharedNotesApp2x val chatApp2x = new ChatApp2x val usersApp = new UsersApp(liveMeeting, outGW, eventBus) val groupChatApp = new GroupChatHdlrs @@ -123,14 +121,6 @@ class MeetingActor( object ExpiryTrackerHelper extends MeetingExpiryTrackerHelper - val inactivityTracker = new MeetingInactivityTracker( - TimeUtil.minutesToMillis(props.durationProps.maxInactivityTimeoutMinutes), - TimeUtil.minutesToMillis(props.durationProps.warnMinutesBeforeMax), - lastActivityTimestampInMs = TimeUtil.timeNowInMs(), - warningSent = false, - warningSentOnTimestampInMs = 0L - ) - val expiryTracker = new MeetingExpiryTracker( startedOnInMs = TimeUtil.timeNowInMs(), userHasJoined = false, @@ -150,7 +140,6 @@ class MeetingActor( new GroupChats(Map.empty), new PresentationPodManager(Map.empty), None, - inactivityTracker, expiryTracker, recordingTracker ) @@ -271,11 +260,6 @@ class MeetingActor( } - private def updateInactivityTracker(state: MeetingState2x): MeetingState2x = { - val tracker = state.inactivityTracker.updateLastActivityTimestamp(TimeUtil.timeNowInMs()) - state.update(tracker) - } - private def updateVoiceUserLastActivity(userId: String) { for { vu <- VoiceUsers.findWithVoiceUserId(liveMeeting.voiceUsers, userId) @@ -294,14 +278,9 @@ class MeetingActor( private def handleBbbCommonEnvCoreMsg(msg: BbbCommonEnvCoreMsg): Unit = { msg.core match { - case m: ClientToServerLatencyTracerMsg => handleMessageThatDoesNotAffectsInactivity(msg) - case _ => handleMessageThatAffectsInactivity(msg) - } - } - - private def handleMessageThatDoesNotAffectsInactivity(msg: BbbCommonEnvCoreMsg): Unit = { - msg.core match { - case m: ClientToServerLatencyTracerMsg => handleClientToServerLatencyTracerMsg(m) + case m: ClientToServerLatencyTracerMsg => handleClientToServerLatencyTracerMsg(m) + case m: CheckRunningAndRecordingVoiceConfEvtMsg => handleCheckRunningAndRecordingVoiceConfEvtMsg(m) + case _ => handleMessageThatAffectsInactivity(msg) } } @@ -318,9 +297,6 @@ class MeetingActor( case m: UserBroadcastCamStartMsg => handleUserBroadcastCamStartMsg(m) case m: UserBroadcastCamStopMsg => handleUserBroadcastCamStopMsg(m) case m: UserJoinedVoiceConfEvtMsg => handleUserJoinedVoiceConfEvtMsg(m) - case m: MeetingActivityResponseCmdMsg => - state = usersApp.handleMeetingActivityResponseCmdMsg(m, state) - state = updateInactivityTracker(state) case m: LogoutAndEndMeetingCmdMsg => usersApp.handleLogoutAndEndMeetingCmdMsg(m, state) case m: SetRecordingStatusCmdMsg => state = usersApp.handleSetRecordingStatusCmdMsg(m, state) @@ -382,7 +358,6 @@ class MeetingActor( case m: UserLeftVoiceConfEvtMsg => handleUserLeftVoiceConfEvtMsg(m) case m: UserMutedInVoiceConfEvtMsg => handleUserMutedInVoiceConfEvtMsg(m) case m: UserTalkingInVoiceConfEvtMsg => - state = updateInactivityTracker(state) updateVoiceUserLastActivity(m.body.voiceUserId) handleUserTalkingInVoiceConfEvtMsg(m) case m: VoiceConfCallStateEvtMsg => handleVoiceConfCallStateEvtMsg(m) @@ -402,8 +377,6 @@ class MeetingActor( case m: UserConnectedToGlobalAudioMsg => handleUserConnectedToGlobalAudioMsg(m) case m: UserDisconnectedFromGlobalAudioMsg => handleUserDisconnectedFromGlobalAudioMsg(m) case m: VoiceConfRunningEvtMsg => handleVoiceConfRunningEvtMsg(m) - case m: CheckRunningAndRecordingVoiceConfEvtMsg => - handleCheckRunningAndRecordingVoiceConfEvtMsg(m) case m: UserStatusVoiceConfEvtMsg => handleUserStatusVoiceConfEvtMsg(m) @@ -448,14 +421,6 @@ class MeetingActor( case m: UpdateCaptionOwnerPubMsg => captionApp2x.handle(m, liveMeeting, msgBus) case m: SendCaptionHistoryReqMsg => captionApp2x.handle(m, liveMeeting, msgBus) - // SharedNotes - case m: GetSharedNotesPubMsg => sharedNotesApp2x.handle(m, liveMeeting, msgBus) - case m: SyncSharedNotePubMsg => sharedNotesApp2x.handle(m, liveMeeting, msgBus) - case m: ClearSharedNotePubMsg => sharedNotesApp2x.handle(m, liveMeeting, msgBus) - case m: UpdateSharedNoteReqMsg => sharedNotesApp2x.handle(m, liveMeeting, msgBus) - case m: CreateSharedNoteReqMsg => sharedNotesApp2x.handle(m, liveMeeting, msgBus) - case m: DestroySharedNoteReqMsg => sharedNotesApp2x.handle(m, liveMeeting, msgBus) - // Guests case m: GetGuestsWaitingApprovalReqMsg => handleGetGuestsWaitingApprovalReqMsg(m) case m: SetGuestPolicyCmdMsg => handleSetGuestPolicyMsg(m) @@ -565,12 +530,9 @@ class MeetingActor( def handleMonitorNumberOfUsers(msg: MonitorNumberOfUsersInternalMsg) { state = removeUsersWithExpiredUserLeftFlag(liveMeeting, state) - val (newState, expireReason) = ExpiryTrackerHelper.processMeetingInactivityAudit(outGW, eventBus, liveMeeting, state) + val (newState, expireReason) = ExpiryTrackerHelper.processMeetingExpiryAudit(outGW, eventBus, liveMeeting, state) state = newState expireReason foreach (reason => log.info("Meeting {} expired with reason {}", props.meetingProp.intId, reason)) - val (newState2, expireReason2) = ExpiryTrackerHelper.processMeetingExpiryAudit(outGW, eventBus, liveMeeting, state) - state = newState2 - expireReason2 foreach (reason => log.info("Meeting {} expired with reason {}", props.meetingProp.intId, reason)) sendRttTraceTest() setRecordingChapterBreak() diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingExpiryTrackerHelper.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingExpiryTrackerHelper.scala index db97ff69f6122f0cabb015265b57d9d09df90de1..21247da7a93338b89c78b0f5a8f5d6577333f959 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingExpiryTrackerHelper.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingExpiryTrackerHelper.scala @@ -27,44 +27,4 @@ trait MeetingExpiryTrackerHelper extends HandlerHelpers { (state, reason) } - - def processMeetingInactivityAudit( - outGW: OutMsgRouter, - eventBus: InternalEventBus, - liveMeeting: LiveMeeting, - state: MeetingState2x - ): (MeetingState2x, Option[String]) = { - - val nowInMs = TimeUtil.timeNowInMs() - if (!state.inactivityTracker.hasRecentActivity(nowInMs)) { - if (state.inactivityTracker.isMeetingInactive(nowInMs)) { - val expireReason = MeetingEndReason.ENDED_DUE_TO_INACTIVITY - endAllBreakoutRooms(eventBus, liveMeeting, state) - sendEndMeetingDueToExpiry(expireReason, eventBus, outGW, liveMeeting) - (state, Some(expireReason)) - } else { - if (!state.inactivityTracker.warningSent) { - val timeLeftSeconds = TimeUtil.millisToSeconds(state.inactivityTracker.timeLeftInMs(nowInMs)) - val event = buildMeetingInactivityWarningEvtMsg(liveMeeting.props.meetingProp.intId, timeLeftSeconds) - outGW.send(event) - val tracker = state.inactivityTracker.setWarningSentAndTimestamp(nowInMs) - (state.update(tracker), None) - } else { - (state, None) - } - } - } else { - (state, None) - } - } - - def buildMeetingInactivityWarningEvtMsg(meetingId: String, timeLeftInSec: Long): BbbCommonEnvCoreMsg = { - val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, "not-used") - val envelope = BbbCoreEnvelope(MeetingInactivityWarningEvtMsg.NAME, routing) - val body = MeetingInactivityWarningEvtMsgBody(timeLeftInSec) - val header = BbbClientMsgHeader(MeetingInactivityWarningEvtMsg.NAME, meetingId, "not-used") - val event = MeetingInactivityWarningEvtMsg(header, body) - - BbbCommonEnvCoreMsg(envelope, event) - } } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/RunningMeeting.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/RunningMeeting.scala index 2d7e54f9ebcfb71b567501b71bdbbaea76c552ae..9d54c7c1847c0b75d8e6d03e1549197035196229 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/RunningMeeting.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/RunningMeeting.scala @@ -22,7 +22,6 @@ class RunningMeeting(val props: DefaultProps, outGW: OutMessageGateway, private val wbModel = new WhiteboardModel() private val presModel = new PresentationModel() private val captionModel = new CaptionModel() - private val notesModel = new SharedNotesModel() private val registeredUsers = new RegisteredUsers private val meetingStatux2x = new MeetingStatus2x private val webcams = new Webcams @@ -38,7 +37,7 @@ class RunningMeeting(val props: DefaultProps, outGW: OutMessageGateway, // easy to test. private val liveMeeting = new LiveMeeting(props, meetingStatux2x, deskshareModel, chatModel, layouts, registeredUsers, polls2x, wbModel, presModel, captionModel, - notesModel, webcams, voiceUsers, users2x, guestsWaiting) + webcams, voiceUsers, users2x, guestsWaiting) GuestsWaiting.setGuestPolicy( liveMeeting.guestsWaiting, diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/AnalyticsActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/AnalyticsActor.scala index 92aa396bd3cb9b5434fa8f64546595bbc8f21343..7c08cdfdfd49facb7e99a966b5a8934f213451b0 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/AnalyticsActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/AnalyticsActor.scala @@ -49,7 +49,6 @@ class AnalyticsActor extends Actor with ActorLogging { case m: UserLeftMeetingEvtMsg => logMessage(msg) case m: PresenterUnassignedEvtMsg => logMessage(msg) case m: PresenterAssignedEvtMsg => logMessage(msg) - case m: MeetingIsActiveEvtMsg => logMessage(msg) case m: UserEjectedFromMeetingEvtMsg => logMessage(msg) case m: EjectUserFromVoiceConfSysMsg => logMessage(msg) case m: CreateBreakoutRoomSysCmdMsg => logMessage(msg) @@ -76,7 +75,6 @@ class AnalyticsActor extends Actor with ActorLogging { case m: ScreenshareStopRtmpBroadcastVoiceConfMsg => logMessage(msg) case m: ScreenshareRtmpBroadcastStartedEvtMsg => logMessage(msg) case m: ScreenshareRtmpBroadcastStoppedEvtMsg => logMessage(msg) - case m: MeetingInactivityWarningEvtMsg => logMessage(msg) case m: StartRecordingVoiceConfSysMsg => logMessage(msg) case m: StopRecordingVoiceConfSysMsg => logMessage(msg) //case m: UpdateRecordingTimerEvtMsg => logMessage(msg) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/MsgBuilder.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/MsgBuilder.scala index f0e671311bcd73d0280c34a45e106e4f890f5ef1..e02174ffaea50f7e9ebdb7e5769421aff77fe6d4 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/MsgBuilder.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/MsgBuilder.scala @@ -44,7 +44,7 @@ object MsgBuilder { val envelope = BbbCoreEnvelope(GetGuestsWaitingApprovalRespMsg.NAME, routing) val header = BbbClientMsgHeader(GetGuestsWaitingApprovalRespMsg.NAME, meetingId, userId) - val guestsWaiting = guests.map(g => GuestWaitingVO(g.intId, g.name, g.role, g.guest, g.authenticated)) + val guestsWaiting = guests.map(g => GuestWaitingVO(g.intId, g.name, g.role, g.guest, g.avatar, g.authenticated)) val body = GetGuestsWaitingApprovalRespMsgBody(guestsWaiting) val event = GetGuestsWaitingApprovalRespMsg(header, body) @@ -56,7 +56,7 @@ object MsgBuilder { val envelope = BbbCoreEnvelope(GuestsWaitingForApprovalEvtMsg.NAME, routing) val header = BbbClientMsgHeader(GuestsWaitingForApprovalEvtMsg.NAME, meetingId, userId) - val guestsWaiting = guests.map(g => GuestWaitingVO(g.intId, g.name, g.role, g.guest, g.authenticated)) + val guestsWaiting = guests.map(g => GuestWaitingVO(g.intId, g.name, g.role, g.guest, g.avatar, g.authenticated)) val body = GuestsWaitingForApprovalEvtMsgBody(guestsWaiting) val event = GuestsWaitingForApprovalEvtMsg(header, body) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/testdata/FakeTestData.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/testdata/FakeTestData.scala index e0966b6bb968052d59e28a0a3ce138bb28e944f4..7bac20ee8bcd5b1b929f2776337d013ff1a9b954 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/testdata/FakeTestData.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/testdata/FakeTestData.scala @@ -20,13 +20,13 @@ trait FakeTestData { val guest1 = createUserVoiceAndCam(liveMeeting, Roles.VIEWER_ROLE, guest = true, authed = true, CallingWith.WEBRTC, muted = false, talking = false, listenOnly = false) Users2x.add(liveMeeting.users2x, guest1) - val guestWait1 = GuestWaiting(guest1.intId, guest1.name, guest1.role, guest1.guest, guest1.authed) + val guestWait1 = GuestWaiting(guest1.intId, guest1.name, guest1.role, guest1.guest, "", guest1.authed) GuestsWaiting.add(liveMeeting.guestsWaiting, guestWait1) val guest2 = createUserVoiceAndCam(liveMeeting, Roles.VIEWER_ROLE, guest = true, authed = true, CallingWith.FLASH, muted = false, talking = false, listenOnly = false) Users2x.add(liveMeeting.users2x, guest2) - val guestWait2 = GuestWaiting(guest2.intId, guest2.name, guest2.role, guest2.guest, guest2.authed) + val guestWait2 = GuestWaiting(guest2.intId, guest2.name, guest2.role, guest2.guest, "", guest2.authed) GuestsWaiting.add(liveMeeting.guestsWaiting, guestWait2) val vu1 = FakeUserGenerator.createFakeVoiceOnlyUser(CallingWith.PHONE, muted = false, talking = false, listenOnly = false) diff --git a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/AppsTestFixtures.scala b/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/AppsTestFixtures.scala index 7f3c3e1a8e6c9c98ec53522252d9bf3087cb33a0..d82f2d88158949416a1445f52094a838bc455d6e 100755 --- a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/AppsTestFixtures.scala +++ b/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/AppsTestFixtures.scala @@ -18,8 +18,6 @@ trait AppsTestFixtures { val muteOnStart = true val deskshareConfId = "85115-DESKSHARE" val durationInMinutes = 10 - val maxInactivityTimeoutMinutes = 120 - val warnMinutesBeforeMax = 30 val meetingExpireIfNoUserJoinedInMinutes = 5 val meetingExpireWhenLastUserLeftInMinutes = 10 val userInactivityInspectTimerInMinutes = 60 @@ -50,7 +48,7 @@ trait AppsTestFixtures { val meetingProp = MeetingProp(name = meetingName, extId = externalMeetingId, intId = meetingId, isBreakout = isBreakout.booleanValue()) - val durationProps = DurationProps(duration = durationInMinutes, createdTime = createTime, createdDate = createDate, maxInactivityTimeoutMinutes = maxInactivityTimeoutMinutes, warnMinutesBeforeMax = warnMinutesBeforeMax, + val durationProps = DurationProps(duration = durationInMinutes, createdTime = createTime, createdDate = createDate, meetingExpireIfNoUserJoinedInMinutes = meetingExpireIfNoUserJoinedInMinutes, meetingExpireWhenLastUserLeftInMinutes = meetingExpireWhenLastUserLeftInMinutes, userInactivityInspectTimerInMinutes = userInactivityInspectTimerInMinutes, userInactivityThresholdInMinutes = userInactivityInspectTimerInMinutes, userActivitySignResponseDelayInMinutes = userActivitySignResponseDelayInMinutes) val password = PasswordProp(moderatorPass = moderatorPassword, viewerPass = viewerPassword) @@ -72,7 +70,6 @@ trait AppsTestFixtures { val presModel = new PresentationModel() val breakoutRooms = new BreakoutRooms() val captionModel = new CaptionModel() - val notesModel = new SharedNotesModel() val registeredUsers = new RegisteredUsers val meetingStatux2x = new MeetingStatus2x val webcams = new Webcams @@ -88,7 +85,6 @@ trait AppsTestFixtures { val wbModel = new WhiteboardModel() val presModel = new PresentationModel() val captionModel = new CaptionModel() - val notesModel = new SharedNotesModel() val registeredUsers = new RegisteredUsers val meetingStatux2x = new MeetingStatus2x val webcams = new Webcams @@ -102,6 +98,6 @@ trait AppsTestFixtures { // easy to test. new LiveMeeting(defaultProps, meetingStatux2x, deskshareModel, chatModel, layouts, registeredUsers, polls2x, wbModel, presModel, captionModel, - notesModel, webcams, voiceUsers, users2x, guestsWaiting) + webcams, voiceUsers, users2x, guestsWaiting) } } diff --git a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/domain/MeetingInactivityTrackerTests.scala b/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/domain/MeetingInactivityTrackerTests.scala deleted file mode 100755 index 95f8ab07d9f674a95c649813b873fe8e5c11c6da..0000000000000000000000000000000000000000 --- a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/domain/MeetingInactivityTrackerTests.scala +++ /dev/null @@ -1,37 +0,0 @@ -package org.bigbluebutton.core.domain - -import org.bigbluebutton.core.UnitSpec -import org.bigbluebutton.core.util.TimeUtil - -class MeetingInactivityTrackerTests extends UnitSpec { - - "A MeetingInactivityTrackerHelper" should "be return meeting is inactive" in { - val nowInMinutes = TimeUtil.minutesToSeconds(15) - - val inactivityTracker = new MeetingInactivityTracker( - maxInactivityTimeoutInMs = 12, - warningBeforeMaxInMs = 2, - lastActivityTimestampInMs = TimeUtil.minutesToSeconds(5), - warningSent = true, - warningSentOnTimestampInMs = 0L - ) - - val active = inactivityTracker.isMeetingInactive(nowInMinutes) - assert(active == true) - } - - "A MeetingInactivityTrackerHelper" should "be return meeting is active" in { - val nowInMinutes = TimeUtil.minutesToSeconds(18) - val inactivityTracker = new MeetingInactivityTracker( - maxInactivityTimeoutInMs = 12, - warningBeforeMaxInMs = 2, - lastActivityTimestampInMs = TimeUtil.minutesToSeconds(5), - warningSent = true, - warningSentOnTimestampInMs = 0L - ) - - val inactive = inactivityTracker.isMeetingInactive(nowInMinutes) - assert(inactive) - } - -} diff --git a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/running/MeetingExpiryTrackerHelperTests.scala b/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/running/MeetingExpiryTrackerHelperTests.scala deleted file mode 100755 index 871dcab1f850379f06da4d0657d231aa58a5280c..0000000000000000000000000000000000000000 --- a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core/running/MeetingExpiryTrackerHelperTests.scala +++ /dev/null @@ -1,38 +0,0 @@ -package org.bigbluebutton.core.running - -import org.bigbluebutton.core.UnitSpec -import org.bigbluebutton.core.domain.MeetingInactivityTracker -import org.bigbluebutton.core.util.TimeUtil - -class MeetingExpiryTrackerHelperTests extends UnitSpec { - - "A MeetingInactivityTrackerHelper" should "be return meeting is inactive" in { - val nowInMinutes = TimeUtil.minutesToSeconds(15) - - val inactivityTracker = new MeetingInactivityTracker( - maxInactivityTimeoutInMs = 12, - warningBeforeMaxInMs = 2, - lastActivityTimestampInMs = TimeUtil.minutesToSeconds(5), - warningSent = true, - warningSentOnTimestampInMs = 0L - ) - - val active = inactivityTracker.isMeetingInactive(nowInMinutes) - assert(active == true) - } - - "A MeetingInactivityTrackerHelper" should "be return meeting is active" in { - val nowInMinutes = TimeUtil.minutesToSeconds(18) - val inactivityTracker = new MeetingInactivityTracker( - maxInactivityTimeoutInMs = 12, - warningBeforeMaxInMs = 2, - lastActivityTimestampInMs = TimeUtil.minutesToSeconds(5), - warningSent = true, - warningSentOnTimestampInMs = 0L - ) - - val inactive = inactivityTracker.isMeetingInactive(nowInMinutes) - assert(inactive) - } - -} diff --git a/akka-bbb-apps/src/universal/conf/application.conf b/akka-bbb-apps/src/universal/conf/application.conf index 4bf02465c110384b5dc578c836d2b4b2cd433ff5..ff2f57e4ecdf6fb5798807d1eaff8f297ec55a40 100755 --- a/akka-bbb-apps/src/universal/conf/application.conf +++ b/akka-bbb-apps/src/universal/conf/application.conf @@ -59,11 +59,6 @@ eventBus { outBbbMsgMsgChannel = "OutBbbMsgChannel" } -sharedNotes { - maxNumberOfNotes = 3 - maxNumberOfUndos = 30 -} - http { interface = "127.0.0.1" port = 9999 diff --git a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/meeting/AllowedMessageNames.scala b/bbb-apps-common/src/main/scala/org/bigbluebutton/client/meeting/AllowedMessageNames.scala index 7a81d284bafc6ce326c912ed9455a0a873525b6a..e26221c1711c6a09ee579aa13b7ca23f71ea6bee 100644 --- a/bbb-apps-common/src/main/scala/org/bigbluebutton/client/meeting/AllowedMessageNames.scala +++ b/bbb-apps-common/src/main/scala/org/bigbluebutton/client/meeting/AllowedMessageNames.scala @@ -22,7 +22,6 @@ object AllowedMessageNames { UserBroadcastCamStopMsg.NAME, LogoutAndEndMeetingCmdMsg.NAME, GetRecordingStatusReqMsg.NAME, - MeetingActivityResponseCmdMsg.NAME, SetRecordingStatusCmdMsg.NAME, EjectUserFromMeetingCmdMsg.NAME, IsMeetingMutedReqMsg.NAME, @@ -88,14 +87,6 @@ object AllowedMessageNames { UpdateCaptionOwnerPubMsg.NAME, EditCaptionHistoryPubMsg.NAME, - // Shared Notes Messages - GetSharedNotesPubMsg.NAME, - CreateSharedNoteReqMsg.NAME, - DestroySharedNoteReqMsg.NAME, - UpdateSharedNoteReqMsg.NAME, - SyncSharedNotePubMsg.NAME, - ClearSharedNotePubMsg.NAME, - // Layout Messages GetCurrentLayoutReqMsg.NAME, BroadcastLayoutMsg.NAME, diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala index 2edcbabaab3256ee3717da0c9e0e85e466c63fc1..805a2ab0af169272c52b3d0474f398c8d8ca28bc 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala @@ -3,7 +3,6 @@ package org.bigbluebutton.common2.domain case class ConfigProps(defaultConfigToken: String, config: String) case class DurationProps(duration: Int, createdTime: Long, createdDate: String, - maxInactivityTimeoutMinutes: Int, warnMinutesBeforeMax: Int, meetingExpireIfNoUserJoinedInMinutes: Int, meetingExpireWhenLastUserLeftInMinutes: Int, userInactivityInspectTimerInMinutes: Int, userInactivityThresholdInMinutes: Int, userActivitySignResponseDelayInMinutes: Int) diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/GuestsMsgs.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/GuestsMsgs.scala index f89f75b0a771d9e80fd109568954d5946128a031..e51e3d0d14f7ebe7bf7855502f13d74f03358e79 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/GuestsMsgs.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/GuestsMsgs.scala @@ -19,7 +19,7 @@ case class GetGuestsWaitingApprovalRespMsg( body: GetGuestsWaitingApprovalRespMsgBody ) extends BbbCoreMsg case class GetGuestsWaitingApprovalRespMsgBody(guests: Vector[GuestWaitingVO]) -case class GuestWaitingVO(intId: String, name: String, role: String, guest: Boolean, authenticated: Boolean) +case class GuestWaitingVO(intId: String, name: String, role: String, guest: Boolean, avatar: String, authenticated: Boolean) /** * Message sent to client for list of guest waiting for approval. This is sent when diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/SharedNotesMsgs.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/SharedNotesMsgs.scala deleted file mode 100644 index d2cde012b11f949961e99807abfa9fb8830d5f5c..0000000000000000000000000000000000000000 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/SharedNotesMsgs.scala +++ /dev/null @@ -1,65 +0,0 @@ -package org.bigbluebutton.common2.msgs - -import scala.collection.immutable.List - -case class Note( - name: String, - document: String, - patchCounter: Int, - undoPatches: List[(String, String)], - redoPatches: List[(String, String)] -) - -case class NoteReport( - name: String, - document: String, - patchCounter: Int, - undo: Boolean, - redo: Boolean -) - -/* In Messages */ -object GetSharedNotesPubMsg { val NAME = "GetSharedNotesPubMsg" } -case class GetSharedNotesPubMsg(header: BbbClientMsgHeader, body: GetSharedNotesPubMsgBody) extends StandardMsg -case class GetSharedNotesPubMsgBody() - -object SyncSharedNotePubMsg { val NAME = "SyncSharedNotePubMsg" } -case class SyncSharedNotePubMsg(header: BbbClientMsgHeader, body: SyncSharedNotePubMsgBody) extends StandardMsg -case class SyncSharedNotePubMsgBody(noteId: String) - -object UpdateSharedNoteReqMsg { val NAME = "UpdateSharedNoteReqMsg" } -case class UpdateSharedNoteReqMsg(header: BbbClientMsgHeader, body: UpdateSharedNoteReqMsgBody) extends StandardMsg -case class UpdateSharedNoteReqMsgBody(noteId: String, patch: String, operation: String) - -object ClearSharedNotePubMsg { val NAME = "ClearSharedNotePubMsg" } -case class ClearSharedNotePubMsg(header: BbbClientMsgHeader, body: ClearSharedNotePubMsgBody) extends StandardMsg -case class ClearSharedNotePubMsgBody(noteId: String) - -object CreateSharedNoteReqMsg { val NAME = "CreateSharedNoteReqMsg" } -case class CreateSharedNoteReqMsg(header: BbbClientMsgHeader, body: CreateSharedNoteReqMsgBody) extends StandardMsg -case class CreateSharedNoteReqMsgBody(noteName: String) - -object DestroySharedNoteReqMsg { val NAME = "DestroySharedNoteReqMsg" } -case class DestroySharedNoteReqMsg(header: BbbClientMsgHeader, body: DestroySharedNoteReqMsgBody) extends StandardMsg -case class DestroySharedNoteReqMsgBody(noteId: String) - -/* Out Messages */ -object GetSharedNotesEvtMsg { val NAME = "GetSharedNotesEvtMsg" } -case class GetSharedNotesEvtMsg(header: BbbClientMsgHeader, body: GetSharedNotesEvtMsgBody) extends StandardMsg -case class GetSharedNotesEvtMsgBody(notesReport: Map[String, NoteReport], isNotesLimit: Boolean) - -object SyncSharedNoteEvtMsg { val NAME = "SyncSharedNoteEvtMsg" } -case class SyncSharedNoteEvtMsg(header: BbbClientMsgHeader, body: SyncSharedNoteEvtMsgBody) extends StandardMsg -case class SyncSharedNoteEvtMsgBody(noteId: String, noteReport: NoteReport) - -object UpdateSharedNoteRespMsg { val NAME = "UpdateSharedNoteRespMsg" } -case class UpdateSharedNoteRespMsg(header: BbbClientMsgHeader, body: UpdateSharedNoteRespMsgBody) extends StandardMsg -case class UpdateSharedNoteRespMsgBody(noteId: String, patch: String, patchId: Int, undo: Boolean, redo: Boolean) - -object CreateSharedNoteRespMsg { val NAME = "CreateSharedNoteRespMsg" } -case class CreateSharedNoteRespMsg(header: BbbClientMsgHeader, body: CreateSharedNoteRespMsgBody) extends StandardMsg -case class CreateSharedNoteRespMsgBody(noteId: String, noteName: String, isNotesLimit: Boolean) - -object DestroySharedNoteRespMsg { val NAME = "DestroySharedNoteRespMsg" } -case class DestroySharedNoteRespMsg(header: BbbClientMsgHeader, body: DestroySharedNoteRespMsgBody) extends StandardMsg -case class DestroySharedNoteRespMsgBody(noteId: String, isNotesLimit: Boolean) \ No newline at end of file diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/SystemMsgs.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/SystemMsgs.scala index 0337441ffd7fb3a8c4087e58d2fa2a32af061837..c5df737ab485fb9f1578a848071389b3986b8efc 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/SystemMsgs.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/SystemMsgs.scala @@ -159,20 +159,6 @@ case class MeetingTimeRemainingUpdateEvtMsg( ) extends BbbCoreMsg case class MeetingTimeRemainingUpdateEvtMsgBody(timeLeftInSec: Long) -object MeetingInactivityWarningEvtMsg { val NAME = "MeetingInactivityWarningEvtMsg" } -case class MeetingInactivityWarningEvtMsg( - header: BbbClientMsgHeader, - body: MeetingInactivityWarningEvtMsgBody -) extends BbbCoreMsg -case class MeetingInactivityWarningEvtMsgBody(timeLeftInSec: Long) - -object MeetingIsActiveEvtMsg { val NAME = "MeetingIsActiveEvtMsg" } -case class MeetingIsActiveEvtMsg( - header: BbbClientMsgHeader, - body: MeetingIsActiveEvtMsgBody -) extends BbbCoreMsg -case class MeetingIsActiveEvtMsgBody(meetingId: String) - object CheckAlivePingSysMsg { val NAME = "CheckAlivePingSysMsg" } case class CheckAlivePingSysMsg( header: BbbCoreBaseHeader, diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMgs.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMgs.scala index d658d5236580558a9890d4d7f01b0e75c3a6d3a5..3d62d9dfa43bc15dc61f60809c548acba2d3599d 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMgs.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMgs.scala @@ -198,13 +198,6 @@ object AssignPresenterReqMsg { val NAME = "AssignPresenterReqMsg" } case class AssignPresenterReqMsg(header: BbbClientMsgHeader, body: AssignPresenterReqMsgBody) extends StandardMsg case class AssignPresenterReqMsgBody(requesterId: String, newPresenterId: String, newPresenterName: String, assignedBy: String) -/** - * Sent from client as a response to inactivity notifaction from server. - */ -object MeetingActivityResponseCmdMsg { val NAME = "MeetingActivityResponseCmdMsg" } -case class MeetingActivityResponseCmdMsg(header: BbbClientMsgHeader, body: MeetingActivityResponseCmdMsgBody) extends StandardMsg -case class MeetingActivityResponseCmdMsgBody(respondedBy: String) - /** * Sent from client to change the role of the user in the meeting. */ diff --git a/bbb-common-message/src/test/scala/org/bigbluebutton/common2/TestFixtures.scala b/bbb-common-message/src/test/scala/org/bigbluebutton/common2/TestFixtures.scala index c7fc088b818abdd5e772186263b366d579c193a8..158a4a324c9231889b45fa466d72529879398b7e 100755 --- a/bbb-common-message/src/test/scala/org/bigbluebutton/common2/TestFixtures.scala +++ b/bbb-common-message/src/test/scala/org/bigbluebutton/common2/TestFixtures.scala @@ -12,8 +12,6 @@ trait TestFixtures { val voiceConfId = "85115" val durationInMinutes = 10 - val maxInactivityTimeoutMinutes = 120 - val warnMinutesBeforeMax = 30 val meetingExpireIfNoUserJoinedInMinutes = 5 val meetingExpireWhenLastUserLeftInMinutes = 10 val userInactivityInspectTimerInMinutes = 60 @@ -43,7 +41,7 @@ trait TestFixtures { isBreakout = isBreakout.booleanValue()) val breakoutProps = BreakoutProps(parentId = parentMeetingId, sequence = sequence, freeJoin = false, breakoutRooms = Vector()) - val durationProps = DurationProps(duration = durationInMinutes, createdTime = createTime, createdDate = createDate, maxInactivityTimeoutMinutes = maxInactivityTimeoutMinutes, warnMinutesBeforeMax = warnMinutesBeforeMax, + val durationProps = DurationProps(duration = durationInMinutes, createdTime = createTime, createdDate = createDate, meetingExpireIfNoUserJoinedInMinutes = meetingExpireIfNoUserJoinedInMinutes, meetingExpireWhenLastUserLeftInMinutes = meetingExpireWhenLastUserLeftInMinutes, userInactivityInspectTimerInMinutes = userInactivityInspectTimerInMinutes, userInactivityThresholdInMinutes = userInactivityInspectTimerInMinutes, userActivitySignResponseDelayInMinutes = userActivitySignResponseDelayInMinutes) val password = PasswordProp(moderatorPass = moderatorPassword, viewerPass = viewerPassword) diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/ApiParams.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/ApiParams.java index 5c2d04245b86f4babe996777ffe26b9ecad65937..5bfa44ec760bc61f1005e1a664caa95b32bf4b39 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/ApiParams.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/ApiParams.java @@ -70,6 +70,16 @@ public class ApiParams { public static final String LOCK_SETTINGS_LOCK_ON_JOIN = "lockSettingsLockOnJoin"; public static final String LOCK_SETTINGS_LOCK_ON_JOIN_CONFIGURABLE = "lockSettingsLockOnJoinConfigurable"; + // New param passed on create call to callback when meeting ends. + // This is a duplicate of the endCallbackUrl meta param as we want this + // param to stay on the server and not propagated to client and recordings. + public static final String MEETING_ENDED_CALLBACK_URL = "meetingEndedURL"; + + // Param to end the meeting when there are no moderators after a certain period of time. + // Needed for classes where teacher gets disconnected and can't get back in. Prevents + // students from running amok. + public static final String END_WHEN_NO_MODERATOR = "endWhenNoModerator"; + private ApiParams() { throw new IllegalStateException("ApiParams is a utility class. Instanciation is forbidden."); } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java index 27127dfc2efddf4c5f6addf6c74ba683f24ce2f2..43b5fd3022c2a4297d029babdafe5854a573e5bf 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java @@ -40,6 +40,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; +import org.apache.commons.lang3.StringUtils; import org.apache.http.client.utils.URIBuilder; import org.bigbluebutton.api.domain.GuestPolicy; import org.bigbluebutton.api.domain.Meeting; @@ -348,7 +349,7 @@ public class MeetingService implements MessageListener { m.getWebcamsOnlyForModerator(), m.getModeratorPassword(), m.getViewerPassword(), m.getCreateTime(), formatPrettyDate(m.getCreateTime()), m.isBreakout(), m.getSequence(), m.isFreeJoin(), m.getMetadata(), m.getGuestPolicy(), m.getWelcomeMessageTemplate(), m.getWelcomeMessage(), m.getModeratorOnlyMessage(), - m.getDialNumber(), m.getMaxUsers(), m.getMaxInactivityTimeoutMinutes(), m.getWarnMinutesBeforeMax(), + m.getDialNumber(), m.getMaxUsers(), m.getMeetingExpireIfNoUserJoinedInMinutes(), m.getmeetingExpireWhenLastUserLeftInMinutes(), m.getUserInactivityInspectTimerInMinutes(), m.getUserInactivityThresholdInMinutes(), m.getUserActivitySignResponseDelayInMinutes(), m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), keepEvents, @@ -778,27 +779,38 @@ public class MeetingService implements MessageListener { String endCallbackUrl = "endCallbackUrl".toLowerCase(); Map<String, String> metadata = m.getMetadata(); - if (!m.isBreakout() && metadata.containsKey(endCallbackUrl)) { - String callbackUrl = metadata.get(endCallbackUrl); - try { + if (!m.isBreakout()) { + if (metadata.containsKey(endCallbackUrl)) { + String callbackUrl = metadata.get(endCallbackUrl); + try { callbackUrl = new URIBuilder(new URI(callbackUrl)) - .addParameter("recordingmarks", m.haveRecordingMarks() ? "true" : "false") - .addParameter("meetingID", m.getExternalId()).build().toURL().toString(); - callbackUrlService.handleMessage(new MeetingEndedEvent(m.getInternalId(), m.getExternalId(), m.getName(), callbackUrl)); - } catch (MalformedURLException e) { - log.error("Malformed URL in callback url=[{}]", callbackUrl, e); - } catch (URISyntaxException e) { - log.error("URI Syntax error in callback url=[{}]", callbackUrl, e); - } catch (Exception e) { - log.error("Error in callback url=[{}]", callbackUrl, e); + .addParameter("recordingmarks", m.haveRecordingMarks() ? "true" : "false") + .addParameter("meetingID", m.getExternalId()).build().toURL().toString(); + MeetingEndedEvent event = new MeetingEndedEvent(m.getInternalId(), m.getExternalId(), m.getName(), callbackUrl); + processMeetingEndedCallback(event); + } catch (Exception e) { + log.error("Error in callback url=[{}]", callbackUrl, e); + } } + if (! StringUtils.isEmpty(m.getMeetingEndedCallbackURL())) { + String meetingEndedCallbackURL = m.getMeetingEndedCallbackURL(); + callbackUrlService.handleMessage(new MeetingEndedEvent(m.getInternalId(), m.getExternalId(), m.getName(), meetingEndedCallbackURL)); + } } processRemoveEndedMeeting(message); } } + private void processMeetingEndedCallback(MeetingEndedEvent event) { + try { + callbackUrlService.handleMessage(event); + } catch (Exception e) { + log.error("Error in callback url=[{}]", event.getCallbackUrl(), e); + } + } + private void userJoined(UserJoined message) { Meeting m = getMeeting(message.meetingId); if (m != null) { @@ -911,7 +923,7 @@ public class MeetingService implements MessageListener { } else { if (message.userId.startsWith("v_")) { // A dial-in user joined the meeting. Dial-in users by convention has userId that starts with "v_". - User vuser = new User(message.userId, message.userId, message.name, "DIAL-IN-USER", "no-avatar-url", + User vuser = new User(message.userId, message.userId, message.name, "DIAL-IN-USER", "", true, GuestPolicy.ALLOW, "DIAL-IN"); vuser.setVoiceJoined(true); m.userJoined(vuser); diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java index fa5b0fbb73970f39d45b1bc62ced9138e20f8bbe..59ec851912b14e0e011f1269eb158c77363a752f 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java @@ -78,6 +78,7 @@ public class ParamsProcessorUtil { private Boolean moderatorsJoinViaHTML5Client; private Boolean attendeesJoinViaHTML5Client; private Boolean allowRequestsWithoutSession; + private Boolean useDefaultAvatar = false; private String defaultAvatarURL; private String defaultConfigURL; private String defaultGuestPolicy; @@ -107,27 +108,26 @@ public class ParamsProcessorUtil { private Long maxPresentationFileUpload = 30000000L; // 30MB - private Integer maxInactivityTimeoutMinutes = 120; private Integer clientLogoutTimerInMinutes = 0; - private Integer warnMinutesBeforeMax = 5; private Integer meetingExpireIfNoUserJoinedInMinutes = 5; private Integer meetingExpireWhenLastUserLeftInMinutes = 1; private Integer userInactivityInspectTimerInMinutes = 120; private Integer userInactivityThresholdInMinutes = 30; private Integer userActivitySignResponseDelayInMinutes = 5; private Boolean defaultAllowDuplicateExtUserid = true; + private Boolean defaultEndWhenNoModerator = false; private String formatConfNum(String s) { if (s.length() > 5) { - Long confNumL = Long.parseLong(s); - - Locale numFormatLocale = new Locale("en", "US"); - String formatPattern = "#,###"; - DecimalFormatSymbols unusualSymbols = new DecimalFormatSymbols(numFormatLocale); - unusualSymbols.setGroupingSeparator(' '); - DecimalFormat numFormatter = new DecimalFormat(formatPattern, unusualSymbols); - numFormatter.setGroupingSize(3); - return numFormatter.format(confNumL); + /* Reverse conference number. + * Put a whitespace every third char. + * Reverse it again to display it correctly. + * Trim leading whitespaces. + * */ + String confNumReversed = new StringBuilder(s).reverse().toString(); + String confNumSplit = confNumReversed.replaceAll("(.{3})", "$1 "); + String confNumL = new StringBuilder(confNumSplit).reverse().toString().trim(); + return confNumL; } return s; @@ -422,6 +422,15 @@ public class ParamsProcessorUtil { } } + boolean endWhenNoModerator = defaultEndWhenNoModerator; + if (!StringUtils.isEmpty(params.get(ApiParams.END_WHEN_NO_MODERATOR))) { + try { + endWhenNoModerator = Boolean.parseBoolean(params.get(ApiParams.END_WHEN_NO_MODERATOR)); + } catch (Exception ex) { + log.warn("Invalid param [endWhenNoModerator] for meeting=[{}]", internalMeetingId); + } + } + String guestPolicy = defaultGuestPolicy; if (!StringUtils.isEmpty(params.get(ApiParams.GUEST_POLICY))) { guestPolicy = params.get(ApiParams.GUEST_POLICY); @@ -456,6 +465,8 @@ public class ParamsProcessorUtil { externalMeetingId = externalHash + "-" + timeStamp; } + String avatarURL = useDefaultAvatar ? defaultAvatarURL : ""; + // Create the meeting with all passed in parameters. Meeting meeting = new Meeting.Builder(externalMeetingId, internalMeetingId, createTime).withName(meetingName) @@ -466,7 +477,7 @@ public class ParamsProcessorUtil { .withBannerText(bannerText).withBannerColor(bannerColor) .withTelVoice(telVoice).withWebVoice(webVoice) .withDialNumber(dialNumber) - .withDefaultAvatarURL(defaultAvatarURL) + .withDefaultAvatarURL(avatarURL) .withAutoStartRecording(autoStartRec) .withAllowStartStopRecording(allowStartStoptRec) .withWebcamsOnlyForModerator(webcamsOnlyForMod) @@ -489,8 +500,11 @@ public class ParamsProcessorUtil { meeting.setModeratorOnlyMessage(moderatorOnlyMessage); } - meeting.setMaxInactivityTimeoutMinutes(maxInactivityTimeoutMinutes); - meeting.setWarnMinutesBeforeMax(warnMinutesBeforeMax); + if (!StringUtils.isEmpty(params.get(ApiParams.MEETING_ENDED_CALLBACK_URL))) { + String meetingEndedCallbackURL = params.get(ApiParams.MEETING_ENDED_CALLBACK_URL); + meeting.setMeetingEndedCallbackURL(meetingEndedCallbackURL); + } + meeting.setMeetingExpireIfNoUserJoinedInMinutes(meetingExpireIfNoUserJoinedInMinutes); meeting.setMeetingExpireWhenLastUserLeftInMinutes(meetingExpireWhenLastUserLeftInMinutes); meeting.setUserInactivityInspectTimerInMinutes(userInactivityInspectTimerInMinutes); @@ -941,6 +955,10 @@ public class ParamsProcessorUtil { this.webcamsOnlyForModerator = webcamsOnlyForModerator; } + public void setUseDefaultAvatar(Boolean value) { + this.useDefaultAvatar = value; + } + public void setdefaultAvatarURL(String url) { this.defaultAvatarURL = url; } @@ -949,26 +967,10 @@ public class ParamsProcessorUtil { this.defaultGuestPolicy = guestPolicy; } - public void setMaxInactivityTimeoutMinutes(Integer value) { - maxInactivityTimeoutMinutes = value; - } - public void setClientLogoutTimerInMinutes(Integer value) { clientLogoutTimerInMinutes = value; } - public void setWarnMinutesBeforeMax(Integer value) { - warnMinutesBeforeMax = value; - } - - public Integer getMaxInactivityTimeoutMinutes() { - return maxInactivityTimeoutMinutes; - } - - public Integer getWarnMinutesBeforeMax() { - return warnMinutesBeforeMax; - } - public void setMeetingExpireWhenLastUserLeftInMinutes(Integer value) { meetingExpireWhenLastUserLeftInMinutes = value; } @@ -1135,4 +1137,10 @@ public class ParamsProcessorUtil { public void setAllowDuplicateExtUserid(Boolean allow) { this.defaultAllowDuplicateExtUserid = allow; } + + public void setEndWhenNoModerator(Boolean val) { + this.defaultEndWhenNoModerator = val; + } + + } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java index 4709fe49dec5701e54bc20d504f428acf22300aa..f4867b2ffeee575db70a6e3a2e33d211f437dbe4 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java @@ -81,8 +81,6 @@ public class Meeting { private Boolean muteOnStart = false; private Boolean allowModsToUnmuteUsers = false; - private Integer maxInactivityTimeoutMinutes = 120; - private Integer warnMinutesBeforeMax = 5; private Integer meetingExpireIfNoUserJoinedInMinutes = 5; private Integer meetingExpireWhenLastUserLeftInMinutes = 1; private Integer userInactivityInspectTimerInMinutes = 120; @@ -94,6 +92,11 @@ public class Meeting { public final Boolean allowDuplicateExtUserid; + private String meetingEndedCallbackURL = ""; + + public final Boolean endWhenNoModerator; + + public Meeting(Meeting.Builder builder) { name = builder.name; extMeetingId = builder.externalId; @@ -122,7 +125,8 @@ public class Meeting { guestPolicy = builder.guestPolicy; breakoutRoomsParams = builder.breakoutRoomsParams; lockSettingsParams = builder.lockSettingsParams; - allowDuplicateExtUserid = builder.allowDuplicateExtUserid; + allowDuplicateExtUserid = builder.allowDuplicateExtUserid; + endWhenNoModerator = builder.endWhenNoModerator; userCustomData = new HashMap<>(); @@ -524,22 +528,6 @@ public class Meeting { userCustomData.put(userID, data); } - public void setMaxInactivityTimeoutMinutes(Integer value) { - maxInactivityTimeoutMinutes = value; - } - - public void setWarnMinutesBeforeMax(Integer value) { - warnMinutesBeforeMax = value; - } - - public Integer getMaxInactivityTimeoutMinutes() { - return maxInactivityTimeoutMinutes; - } - - public Integer getWarnMinutesBeforeMax() { - return warnMinutesBeforeMax; - } - public void setMeetingExpireWhenLastUserLeftInMinutes(Integer value) { meetingExpireWhenLastUserLeftInMinutes = value; } @@ -581,6 +569,14 @@ public class Meeting { this.userActivitySignResponseDelayInMinutes = userActivitySignResponseDelayInMinutes; } + public String getMeetingEndedCallbackURL() { + return meetingEndedCallbackURL; + } + + public void setMeetingEndedCallbackURL(String meetingEndedCallbackURL) { + this.meetingEndedCallbackURL = meetingEndedCallbackURL; + } + public Map<String, Object> getUserCustomData(String userID){ return (Map<String, Object>) userCustomData.get(userID); } @@ -630,6 +626,7 @@ public class Meeting { private BreakoutRoomsParams breakoutRoomsParams; private LockSettingsParams lockSettingsParams; private Boolean allowDuplicateExtUserid; + private Boolean endWhenNoModerator; public Builder(String externalId, String internalId, long createTime) { this.externalId = externalId; @@ -761,6 +758,11 @@ public class Meeting { this.allowDuplicateExtUserid = allowDuplicateExtUserid; return this; } + + public Builder withEndWhenNoModerator(Boolean endWhenNoModerator) { + this.endWhenNoModerator = endWhenNoModerator; + return this; + } public Meeting build() { return new Meeting(this); diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api2/IBbbWebApiGWApp.java b/bbb-common-web/src/main/java/org/bigbluebutton/api2/IBbbWebApiGWApp.java index be5c15f991d5389b1396246cb8f1471f351c39c6..0cd5d114f0f86aeb447834de83e4cb3539f5d372 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api2/IBbbWebApiGWApp.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api2/IBbbWebApiGWApp.java @@ -21,7 +21,6 @@ public interface IBbbWebApiGWApp { String createDate, Boolean isBreakout, Integer sequence, Boolean freejoin, Map<String, String> metadata, String guestPolicy, String welcomeMsgTemplate, String welcomeMsg, String modOnlyMessage, String dialNumber, Integer maxUsers, - Integer maxInactivityTimeoutMinutes, Integer warnMinutesBeforeMax, Integer meetingExpireIfNoUserJoinedInMinutes, Integer meetingExpireWhenLastUserLeftInMinutes, Integer userInactivityInspectTimerInMinutes, diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/ThumbnailCreatorImp.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/ThumbnailCreatorImp.java index 59f04bb89441a41249d09af6bed19c07672384e9..6f1582d942016e489ba79bb1dc2fd5dcf135913b 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/ThumbnailCreatorImp.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/ThumbnailCreatorImp.java @@ -64,7 +64,9 @@ public class ThumbnailCreatorImp implements ThumbnailCreator { renameThumbnails(thumbsDir, page); // Create blank thumbnails for pages that failed to generate a thumbnail. - createBlankThumbnail(thumbsDir, page); + if (!success) { + createBlankThumbnail(thumbsDir, page); + } return success; @@ -117,9 +119,9 @@ public class ThumbnailCreatorImp implements ThumbnailCreator { * If more than 1 file, filename like 'temp-thumb-X.png' else filename is * 'temp-thumb.png' */ + Matcher matcher; if (dir.list().length > 1) { File[] files = dir.listFiles(); - Matcher matcher; for (File file : files) { matcher = PAGE_NUMBER_PATTERN.matcher(file.getAbsolutePath()); if (matcher.matches()) { @@ -144,6 +146,15 @@ public class ThumbnailCreatorImp implements ThumbnailCreator { File oldFilename = new File( dir.getAbsolutePath() + File.separatorChar + dir.list()[0]); String newFilename = "thumb-1.png"; + + // Might be the first thumbnail of a set and it might be out of order + // Avoid setting the second/third/... slide as thumb-1.png + matcher = PAGE_NUMBER_PATTERN.matcher(oldFilename.getAbsolutePath()); + if (matcher.matches()) { + int pageNum = Integer.valueOf(matcher.group(2).trim()).intValue(); + newFilename = "thumb-" + (pageNum) + ".png"; + } + File renamedFile = new File( oldFilename.getParent() + File.separatorChar + newFilename); oldFilename.renameTo(renamedFile); diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala index ac6ec822bd7a5cf32e89ed40f92657bf05dd23be..bb98953624cb95a0b4ee15a9bb68b2e3eca43f20 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala @@ -129,8 +129,7 @@ class BbbWebApiGWApp( freeJoin: java.lang.Boolean, metadata: java.util.Map[String, String], guestPolicy: String, welcomeMsgTemplate: String, welcomeMsg: String, modOnlyMessage: String, - dialNumber: String, maxUsers: java.lang.Integer, maxInactivityTimeoutMinutes: java.lang.Integer, - warnMinutesBeforeMax: java.lang.Integer, + dialNumber: String, maxUsers: java.lang.Integer, meetingExpireIfNoUserJoinedInMinutes: java.lang.Integer, meetingExpireWhenLastUserLeftInMinutes: java.lang.Integer, userInactivityInspectTimerInMinutes: java.lang.Integer, @@ -147,8 +146,6 @@ class BbbWebApiGWApp( val durationProps = DurationProps( duration = duration.intValue(), createdTime = createTime.longValue(), createDate, - maxInactivityTimeoutMinutes = maxInactivityTimeoutMinutes.intValue(), - warnMinutesBeforeMax = warnMinutesBeforeMax.intValue(), meetingExpireIfNoUserJoinedInMinutes = meetingExpireIfNoUserJoinedInMinutes.intValue(), meetingExpireWhenLastUserLeftInMinutes = meetingExpireWhenLastUserLeftInMinutes.intValue(), userInactivityInspectTimerInMinutes = userInactivityInspectTimerInMinutes.intValue(), diff --git a/bbb-libreoffice/assets/libreoffice_container.sh b/bbb-libreoffice/assets/libreoffice_container.sh index 02e1ef0ee9c7defb314fc8641c31d8cee9c76177..dc1a4454c6109b420012a59bd475b1056d0ff08f 100755 --- a/bbb-libreoffice/assets/libreoffice_container.sh +++ b/bbb-libreoffice/assets/libreoffice_container.sh @@ -35,7 +35,7 @@ if (($INSTANCE_NUMBER >= 1)); then iptables -C $FORWARD_RULE || iptables -I $FORWARD_RULE - docker run --network bbb-libreoffice --name bbb-libreoffice-${INSTANCE_NUMBER} -p $PORT:8000 -v${SOFFICE_WORK_DIR}:${SOFFICE_WORK_DIR} --rm bbb-libreoffice & + docker run --network bbb-libreoffice --user `id -u bigbluebutton` --name bbb-libreoffice-${INSTANCE_NUMBER} -p $PORT:8000 -v${SOFFICE_WORK_DIR}:${SOFFICE_WORK_DIR} --rm bbb-libreoffice & wait $! else diff --git a/bbb-libreoffice/docker/Dockerfile b/bbb-libreoffice/docker/Dockerfile index 30f4f67e6c7afbe90c4f5dde4dada182c63ee83e..0a9170018ef1c453b4178f0aa53eae7605ec84cd 100644 --- a/bbb-libreoffice/docker/Dockerfile +++ b/bbb-libreoffice/docker/Dockerfile @@ -4,26 +4,13 @@ ENV DEBIAN_FRONTEND noninteractive RUN apt update -ARG user_id -RUN echo "User id = $user_id" - -RUN addgroup --system --gid $user_id libreoffice - -# We need to ensure that this user id is the same as the user bigbluebutton in the host -RUN adduser --disabled-password --system --disabled-login --shell /sbin/nologin --gid $user_id --uid $user_id libreoffice - RUN apt -y install locales-all fontconfig libxt6 libxrender1 -RUN apt -y install libreoffice --no-install-recommends +RUN apt -y install --no-install-recommends libreoffice fonts-crosextra-carlito fonts-crosextra-caladea fonts-noto + RUN dpkg-reconfigure fontconfig && fc-cache -f -s -v VOLUME ["/usr/share/fonts/"] -RUN chown libreoffice /home/libreoffice/ - -ADD ./bbb-libreoffice-entrypoint.sh /home/libreoffice/ -RUN chown -R libreoffice /home/libreoffice/ -RUN chmod 700 /home/libreoffice/bbb-libreoffice-entrypoint.sh - -USER libreoffice +ADD ./bbb-libreoffice-entrypoint.sh /usr/local/bin/ -ENTRYPOINT ["/home/libreoffice/bbb-libreoffice-entrypoint.sh" ] +ENTRYPOINT ["/usr/local/bin/bbb-libreoffice-entrypoint.sh" ] diff --git a/bbb-libreoffice/docker/bbb-libreoffice-entrypoint.sh b/bbb-libreoffice/docker/bbb-libreoffice-entrypoint.sh old mode 100644 new mode 100755 diff --git a/bbb-libreoffice/install.sh b/bbb-libreoffice/install.sh index a1a2c075158060786e31b542602f5271343641ae..c4aa96253730d0a581114dc1577b703d3c624c29 100755 --- a/bbb-libreoffice/install.sh +++ b/bbb-libreoffice/install.sh @@ -25,7 +25,7 @@ fi IMAGE_CHECK=`docker image inspect bbb-libreoffice &> /dev/null && echo 1 || echo 0` if [ "$IMAGE_CHECK" = "0" ]; then echo "Docker image doesn't exists, building" - docker build -t bbb-libreoffice --build-arg user_id=`id -u bigbluebutton` docker/ + docker build -t bbb-libreoffice docker/ else echo "Docker image already exists"; fi diff --git a/bbb-webhooks/package-lock.json b/bbb-webhooks/package-lock.json index 668a04581341c35db5a289e67c42807e46b89870..81b685e5f327588946ff5df33ca318df91e080a2 100644 --- a/bbb-webhooks/package-lock.json +++ b/bbb-webhooks/package-lock.json @@ -725,7 +725,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "requires": { "depd": "~1.1.2", @@ -848,9 +848,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" }, "lodash.get": { "version": "4.4.2", @@ -883,7 +883,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "merge-descriptors": { @@ -923,16 +923,19 @@ "brace-expansion": "^1.1.7" } }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz" - }, "mkdirp": { "version": "0.5.1", "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } } }, "mocha": { diff --git a/bbb-webhooks/package.json b/bbb-webhooks/package.json old mode 100755 new mode 100644 index 1af75a84cc2bb25ce6c4454ce622c3bf0ff69a33..d896daea56648a9c1ee1a8e83d31f4c76f40e615 --- a/bbb-webhooks/package.json +++ b/bbb-webhooks/package.json @@ -13,7 +13,7 @@ "config": "1.30.0", "express": "4.16.4", "js-yaml": "^3.13.1", - "lodash": "^4.17.15", + "lodash": "^4.17.19", "nock": "^10.0.4", "redis": "^2.8.0", "request": "2.88.0", diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/events/BBBEvent.as b/bigbluebutton-client/src/org/bigbluebutton/main/events/BBBEvent.as index b8409bba7b2113b594d820f47a714ceb5a99d3ec..36bddc5d42986cce80abecbec3e4305fa02934e8 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/events/BBBEvent.as +++ b/bigbluebutton-client/src/org/bigbluebutton/main/events/BBBEvent.as @@ -24,9 +24,6 @@ package org.bigbluebutton.main.events { public static const END_MEETING_EVENT:String = 'END_MEETING_EVENT'; public static const LOGOUT_END_MEETING_EVENT:String = 'LOGOUT_END_MEETING_EVENT'; public static const CONFIRM_LOGOUT_END_MEETING_EVENT:String = 'CONFIRM_LOGOUT_END_MEETING_EVENT'; - public static const INACTIVITY_WARNING_EVENT:String = 'INACTIVITY_WARNING_EVENT'; - public static const ACTIVITY_RESPONSE_EVENT:String = 'ACTIVITY_RESPONSE_EVENT'; - public static const MEETING_IS_ACTIVE_EVENT:String = 'MEETING_IS_ACTIVE_EVENT'; public static const LOGIN_EVENT:String = 'loginEvent'; public static const RECEIVED_PUBLIC_CHAT_MESSAGE_EVENT:String = 'RECEIVED_PUBLIC_CHAT_MESSAGE_EVENT'; public static const SEND_PUBLIC_CHAT_MESSAGE_EVENT:String = 'SEND_PUBLIC_CHAT_MESSAGE_EVENT'; diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/UserService.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/UserService.as index 4d2f30ff555c178f6176479f6e5362625dbe1096..15efcb0ed5570629d0924ebf65a68cc15c7ec3c2 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/UserService.as +++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/UserService.as @@ -190,10 +190,6 @@ package org.bigbluebutton.main.model.users } } - public function activityResponse():void { - sender.activityResponse(); - } - public function userActivitySignResponse():void { sender.userActivitySignResponse(); } diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/InactivityWarningWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/InactivityWarningWindow.mxml deleted file mode 100755 index c90670970ae83d9dcba7bf15e8eafd740fc5744a..0000000000000000000000000000000000000000 --- a/bigbluebutton-client/src/org/bigbluebutton/main/views/InactivityWarningWindow.mxml +++ /dev/null @@ -1,109 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> - -<!-- - -BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ - -Copyright (c) 2015 BigBlueButton Inc. and by respective authors (see below). - -This program is free software; you can redistribute it and/or modify it under the -terms of the GNU Lesser General Public License as published by the Free Software -Foundation; either version 3.0 of the License, or (at your option) any later -version. - -BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY -WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. - ---> - -<mx:TitleWindow xmlns:mx="library://ns.adobe.com/flex/mx" - xmlns:fx="http://ns.adobe.com/mxml/2009" - xmlns:mate="http://mate.asfusion.com/" - xmlns:common="org.bigbluebutton.common.*" - verticalScrollPolicy="off" - horizontalScrollPolicy="off" - horizontalAlign="center" - minWidth="500" - creationComplete="onCreationComplete()"> - - <fx:Declarations> - <mate:Listener type="{BBBEvent.MEETING_IS_ACTIVE_EVENT}" method="meetingIsActiveFeedback"/> - </fx:Declarations> - - <fx:Script> - <![CDATA[ - import com.asfusion.mate.events.Dispatcher; - - import mx.managers.PopUpManager; - - import org.as3commons.logging.api.ILogger; - import org.as3commons.logging.api.getClassLogger; - import org.bigbluebutton.main.events.BBBEvent; - import org.bigbluebutton.util.i18n.ResourceUtil; - - private static const LOGGER:ILogger = getClassLogger(InactivityWarningWindow); - - public var duration:Number = 0; - private var tickTimer:Timer; - - [Bindable] - private var cancelButtonLabel:String = ResourceUtil.getInstance().getString('bbb.inactivityWarning.cancel'); - - private function onCreationComplete():void { - tickTimer = new Timer(1000, 0); - tickTimer.addEventListener(TimerEvent.TIMER, tick); - - cancelButton.width = cancelButton.measureText(genCancelButtonLabel(duration)).width - + cancelButton.getStyle("paddingRight") - + cancelButton.getStyle("paddingLeft") - + 8; // 8 is magic number - - tickTimer.start(); - cancelButton.visible = true; - } - - private function tick(e:TimerEvent):void { - if (duration > 0) { - cancelButton.label = genCancelButtonLabel(duration); - warningMessage.text = genWarningMessageText(duration); - duration--; - } else { - tickTimer.stop(); - cancelButton.visible = false; - cancelButton.includeInLayout = false; - warningMessage.text = ResourceUtil.getInstance().getString('bbb.shuttingDown.message'); - } - } - - private function genWarningMessageText(timeLeft:Number):String { - return ResourceUtil.getInstance().getString('bbb.inactivityWarning.message', [timeLeft.toString()]); - } - - private function genCancelButtonLabel(timeLeft:Number):String { - return cancelButtonLabel; // + " (" + timeLeft.toString() + ")"; - } - - private function meetingIsActiveFeedback(e:BBBEvent):void { - tickTimer.stop(); - PopUpManager.removePopUp(this); - } - - private function cancelButtonClicked():void { - var dispatcher:Dispatcher = new Dispatcher(); - var bbbEvent:BBBEvent = new BBBEvent(BBBEvent.ACTIVITY_RESPONSE_EVENT); - dispatcher.dispatchEvent(bbbEvent); - } - ]]> - </fx:Script> - <mx:VBox width="100%" height="100%" paddingBottom="5" paddingLeft="5" paddingRight="5" paddingTop="5" horizontalAlign="center" verticalAlign="middle"> - <common:AdvancedLabel text="{ResourceUtil.getInstance().getString('bbb.inactivityWarning.title')}" - styleName="titleWindowStyle" - width="{this.width - 40}" /> - <mx:Text id="warningMessage" selectable="false"/> - <mx:Button id="cancelButton" click="cancelButtonClicked()" visible="false" styleName="mainActionButton"/> - </mx:VBox> -</mx:TitleWindow> \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml index 162c8f26895c3cff7578ae034fa675fcfd2341fe..080850de2ac7c009d2570673d0aab4852e980341 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml @@ -61,7 +61,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <mate:Listener type="{ToolbarButtonEvent.REMOVE}" method="handleRemoveToolbarComponent"/> <mate:Listener type="{ShortcutEvent.OPEN_SHORTCUT_WIN}" method="openShortcutHelpWindow" /> <mate:Listener type="{BBBEvent.OPEN_WEBCAM_PREVIEW}" method="openVideoPreviewWindow" /> - <mate:Listener type="{BBBEvent.INACTIVITY_WARNING_EVENT}" method="handleInactivityWarningEvent" /> <mate:Listener type="{BBBEvent.USER_INACTIVITY_INSPECT_EVENT}" method="handleUserInactivityInspectEvent" /> <mate:Listener type="{LockControlEvent.OPEN_LOCK_SETTINGS}" method="openLockSettingsWindow" /> <mate:Listener type="{BreakoutRoomEvent.OPEN_BREAKOUT_ROOMS_PANEL}" method="openBreakoutRoomsWindow" /> @@ -517,11 +516,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. inactivityWarning.duration = e.payload.responseDelay; } - private function handleInactivityWarningEvent(e:BBBEvent):void { - var inactivityWarning:InactivityWarningWindow = PopUpUtil.createModalPopUp(FlexGlobals.topLevelApplication as DisplayObject, InactivityWarningWindow, true) as InactivityWarningWindow; - inactivityWarning.duration = e.payload.duration; - } - private function handleFlashMicSettingsEvent(event:FlashMicSettingsEvent):void { /** * There is a bug in Flex SDK 4.14 where the screen stays blurry if a diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/monitoring/maps/MonitoringEventMap.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/monitoring/maps/MonitoringEventMap.mxml index 4270c2cf9746ee28191abf54082dbe59abfa6788..469fcd3d9a465495c9f78fbf82e52386bf19d444 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/monitoring/maps/MonitoringEventMap.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/monitoring/maps/MonitoringEventMap.mxml @@ -24,10 +24,6 @@ <InlineInvoker method="{MonitoringModule.handleStreamEventStarted}"/> </EventHandlers> - <EventHandlers type="{BBBEvent.ACTIVITY_RESPONSE_EVENT}" > - <InlineInvoker method="{MonitoringModule.handleAddedListenerEvent}" arguments="{[event]}" /> - </EventHandlers> - <EventHandlers type="{BBBEvent.VIDEO_STARTED}" > <InlineInvoker method="{MonitoringModule.videoHasStarted}" arguments="{[event]}" /> </EventHandlers> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/users/maps/UsersMainEventMap.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/users/maps/UsersMainEventMap.mxml index 141a45fef336e7428e2bc8710ae179dd6ce8bbf5..3e7263689f5b71e00932a547d43c22aeb96a9eb9 100644 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/users/maps/UsersMainEventMap.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/users/maps/UsersMainEventMap.mxml @@ -120,10 +120,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <MethodInvoker generator="{UserService}" method="recordAndClearPreviousMarkers" arguments="{event}" /> </EventHandlers> - <EventHandlers type="{BBBEvent.ACTIVITY_RESPONSE_EVENT}"> - <MethodInvoker generator="{UserService}" method="activityResponse" /> - </EventHandlers> - <EventHandlers type="{BBBEvent.USER_ACTIVITY_SIGN_RESPONSE_EVENT}"> <MethodInvoker generator="{UserService}" method="userActivitySignResponse" /> </EventHandlers> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageReceiver.as b/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageReceiver.as index ddf6fc5d6baf5c1881cd1f7d8ce9f89e589fbffa..fa377cf627a33dd9be53151abbe1e19fad04c88d 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageReceiver.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageReceiver.as @@ -137,12 +137,6 @@ package org.bigbluebutton.modules.users.services case "IsMeetingMutedRespMsg": handleIsMeetingMutedResp(message); break; - case "MeetingInactivityWarningEvtMsg": - handleInactivityWarning(message); - break; - case "MeetingIsActiveEvtMsg": - handleMeetingIsActive(message); - break; case "UserEmojiChangedEvtMsg": handleEmojiStatusHand(message); break; @@ -757,19 +751,6 @@ package org.bigbluebutton.modules.users.services } } - private function handleInactivityWarning(msg:Object):void { - var body:Object = msg.body as Object; - - var bbbEvent:BBBEvent = new BBBEvent(BBBEvent.INACTIVITY_WARNING_EVENT); - bbbEvent.payload.duration = body.timeLeftInSec as Number; - globalDispatcher.dispatchEvent(bbbEvent); - } - - private function handleMeetingIsActive(msg:Object):void { - var bbbEvent:BBBEvent = new BBBEvent(BBBEvent.MEETING_IS_ACTIVE_EVENT); - globalDispatcher.dispatchEvent(bbbEvent); - } - private function handleGetRecordingStatusReply(msg: Object):void { var body:Object = msg.body as Object; var recording: Boolean = body.recording as Boolean; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageSender.as b/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageSender.as index 8a4b6323913d8b8c1d916a0d63dbf87c6c888e20..ba686e139a997ced16d7d82aaa6d9381b94317c4 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageSender.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageSender.as @@ -306,27 +306,6 @@ package org.bigbluebutton.modules.users.services }, message); } - public function activityResponse():void { - var message:Object = { - header: {name: "MeetingActivityResponseCmdMsg", meetingId: UsersUtil.getInternalMeetingID(), - userId: UsersUtil.getMyUserID()}, - body: {respondedBy: UsersUtil.getMyUserID()} - }; - - var _nc:ConnectionManager = BBB.initConnectionManager(); - _nc.sendMessage2x( - function(result:String):void { // On successful result - }, - function(status:String):void { // status - On error occurred - var logData:Object = UsersUtil.initLogData(); - logData.tags = ["apps"]; - logData.logCode = "error_sending_meeting_activity_response"; - LOGGER.info(JSON.stringify(logData)); - }, - message - ); //_netConnection.call - } - public function userActivitySignResponse():void { var message:Object = { header: {name: "UserActivitySignCmdMsg", meetingId: UsersUtil.getInternalMeetingID(), diff --git a/bigbluebutton-config/bigbluebutton-release b/bigbluebutton-config/bigbluebutton-release index 2d1a2c0c9e1eeeca9dbb35b798721c5c0f688568..7eaaa8ccdda6d216b95cfab9905f5520cbc31c10 100644 --- a/bigbluebutton-config/bigbluebutton-release +++ b/bigbluebutton-config/bigbluebutton-release @@ -1 +1,2 @@ BIGBLUEBUTTON_RELEASE=2.3.0-dev + diff --git a/bigbluebutton-config/bin/apply-lib.sh b/bigbluebutton-config/bin/apply-lib.sh index a1a7173f5789c8318dbd39a2af1618238c103812..7d884bb89549aa5d20b712474806c0ab16023bd7 100644 --- a/bigbluebutton-config/bin/apply-lib.sh +++ b/bigbluebutton-config/bin/apply-lib.sh @@ -5,8 +5,8 @@ # which (if exists) will be run by `bbb-conf --setip` and `bbb-conf --restart` before restarting # BigBlueButton. # -# The purpose of apply-config.sh is to make it easy for you apply defaults to BigBlueButton server that get applied after -# each package update (since the last step in doing an upate is to run `bbb-conf --setip`. +# The purpose of apply-config.sh is to make it easy to apply your configuration changes to a BigBlueButton server +# before BigBlueButton starts # @@ -74,7 +74,19 @@ HERE } -# Enable firewall rules to lock down access to server +enableHTML5CameraQualityThresholds() { + echo " - Enable HTML5 cameraQualityThresholds" + yq w -i $HTML5_CONFIG public.kurento.cameraQualityThresholds.enabled true +} + +enableHTML5WebcamPagination() { + echo " - Enable HTML5 webcam pagination" + yq w -i $HTML5_CONFIG public.kurento.pagination.enabled true +} + + +# +# Enable firewall rules to open only # enableUFWRules() { echo " - Enable Firewall and opening 22/tcp, 80/tcp, 443/tcp and 16384:32768/udp" @@ -90,6 +102,123 @@ enableUFWRules() { } +enableMultipleKurentos() { + echo " - Configuring three Kurento Media Servers: one for listen only, webcam, and screeshare" + + # Step 1. Setup shared certificate between FreeSWITCH and Kurento + + HOSTNAME=$(cat /etc/nginx/sites-available/bigbluebutton | grep -v '#' | sed -n '/server_name/{s/.*server_name[ ]*//;s/;//;p}' | cut -d' ' -f1 | head -n 1) + openssl req -x509 -new -nodes -newkey rsa:2048 -sha256 -days 3650 -subj "/C=BR/ST=Ottawa/O=BigBlueButton Inc./OU=Live/CN=$HOSTNAME" -keyout /tmp/dtls-srtp-key.pem -out /tmp/dtls-srtp-cert.pem + cat /tmp/dtls-srtp-key.pem /tmp/dtls-srtp-cert.pem > /etc/kurento/dtls-srtp.pem + cat /tmp/dtls-srtp-key.pem /tmp/dtls-srtp-cert.pem > /opt/freeswitch/etc/freeswitch/tls/dtls-srtp.pem + + sed -i 's/;pemCertificateRSA=.*/pemCertificateRSA=\/etc\/kurento\/dtls-srtp.pem/g' /etc/kurento/modules/kurento/WebRtcEndpoint.conf.ini + + # Step 2. Setup systemd unit files to launch three separate instances of Kurento + + for i in `seq 8888 8890`; do + + cat > /usr/lib/systemd/system/kurento-media-server-${i}.service << HERE + # /usr/lib/systemd/system/kurento-media-server-#{i}.service + [Unit] + Description=Kurento Media Server daemon (${i}) + After=network.target + PartOf=kurento-media-server.service + After=kurento-media-server.service + + [Service] + UMask=0002 + Environment=KURENTO_LOGS_PATH=/var/log/kurento-media-server + Environment=KURENTO_CONF_FILE=/etc/kurento/kurento-${i}.conf.json + User=kurento + Group=kurento + LimitNOFILE=1000000 + ExecStartPre=-/bin/rm -f /var/kurento/.cache/gstreamer-1.5/registry.x86_64.bin + ExecStart=/usr/bin/kurento-media-server --gst-debug-level=3 --gst-debug="3,Kurento*:4,kms*:4,KurentoWebSocketTransport:5" + Type=simple + PIDFile=/var/run/kurento-media-server-${i}.pid + Restart=always + + [Install] + WantedBy=kurento-media-server.service + +HERE + + # Make a new configuration file each instance of Kurento that binds to a different port + cp /etc/kurento/kurento.conf.json /etc/kurento/kurento-${i}.conf.json + sed -i "s/8888/${i}/g" /etc/kurento/kurento-${i}.conf.json + + done + + # Step 3. Override the main kurento-media-server unit to start/stop the three Kurento instances + + cat > /etc/systemd/system/kurento-media-server.service << HERE + [Unit] + Description=Kurento Media Server + + [Service] + Type=oneshot + ExecStart=/bin/true + RemainAfterExit=yes + + [Install] + WantedBy=multi-user.target +HERE + + systemctl daemon-reload + + for i in `seq 8888 8890`; do + systemctl enable kurento-media-server-${i}.service + done + + + # Step 4. Modify bbb-webrtc-sfu config to use the three Kurento servers + + KURENTO_CONFIG=/usr/local/bigbluebutton/bbb-webrtc-sfu/config/default.yml + + MEDIA_TYPE=(main audio content) + IP=$(yq r /usr/local/bigbluebutton/bbb-webrtc-sfu/config/default.yml kurento[0].ip) + + for i in `seq 0 2`; do + yq w -i $KURENTO_CONFIG "kurento[$i].ip" $IP + yq w -i $KURENTO_CONFIG "kurento[$i].url" "ws://127.0.0.1:$(($i + 8888))/kurento" + yq w -i $KURENTO_CONFIG "kurento[$i].mediaType" "${MEDIA_TYPE[$i]}" + yq w -i $KURENTO_CONFIG "kurento[$i].ipClassMappings.local" "" + yq w -i $KURENTO_CONFIG "kurento[$i].ipClassMappings.private" "" + yq w -i $KURENTO_CONFIG "kurento[$i].ipClassMappings.public" "" + yq w -i $KURENTO_CONFIG "kurento[$i].options.failAfter" 5 + yq w -i $KURENTO_CONFIG "kurento[$i].options.request_timeout" 30000 + yq w -i $KURENTO_CONFIG "kurento[$i].options.response_timeout" 30000 + done + + yq w -i $KURENTO_CONFIG balancing-strategy MEDIA_TYPE +} + +disableMultipleKurentos() { + echo " - Configuring a single Kurento Media Server for listen only, webcam, and screeshare" + systemctl stop kurento-media-server.service + + for i in `seq 8888 8890`; do + systemctl disable kurento-media-server-${i}.service + done + + # Remove the overrride (restoring the original kurento-media-server.service unit file) + rm -f /etc/systemd/system/kurento-media-server.service + systemctl daemon-reload + + # Restore bbb-webrtc-sfu configuration to use a single instance of Kurento + KURENTO_CONFIG=/usr/local/bigbluebutton/bbb-webrtc-sfu/config/default.yml + yq d -i $KURENTO_CONFIG kurento[1] + yq d -i $KURENTO_CONFIG kurento[1] + + yq w -i $KURENTO_CONFIG "kurento[0].url" "ws://127.0.0.1:8888/kurento" + yq w -i $KURENTO_CONFIG "kurento[0].mediaType" "" + + yq w -i $KURENTO_CONFIG balancing-strategy ROUND_ROBIN +} + + + notCalled() { # # This function is not called. @@ -112,6 +241,9 @@ source /etc/bigbluebutton/bbb-conf/apply-lib.sh #enableHTML5ClientLog #enableUFWRules +#enableHTML5CameraQualityThresholds +#enableHTML5WebcamPagination + HERE chmod +x /etc/bigbluebutton/bbb-conf/apply-config.sh ## Stop Copying HERE diff --git a/bigbluebutton-config/bin/bbb-conf b/bigbluebutton-config/bin/bbb-conf index d153f44c7752c0e1a75af39cdcacc79409f69d01..f8b3bb3d92ea545c33922ad75f6de223a19c8ff6 100755 --- a/bigbluebutton-config/bin/bbb-conf +++ b/bigbluebutton-config/bin/bbb-conf @@ -446,7 +446,7 @@ start_bigbluebutton () { } display_bigbluebutton_status () { - units="nginx freeswitch $REDIS_SERVICE bbb-apps-akka bbb-transcode-akka bbb-fsesl-akka" + units="nginx freeswitch $REDIS_SERVICE bbb-apps-akka bbb-fsesl-akka" if [ -f /usr/share/red5/red5-server.jar ]; then units="$units red5" @@ -1721,8 +1721,9 @@ if [ $ZIP ]; then tar rf $TMP_LOG_FILE /var/log/kurento-media-server > /dev/null 2>&1 tar rf $TMP_LOG_FILE /var/log/mongodb > /dev/null 2>&1 tar rf $TMP_LOG_FILE /var/log/redis > /dev/null 2>&1 - tar rf $TMP_LOG_FILE /var/log/nginx/error.log > /dev/null 2>&1 - tar rfh $TMP_LOG_FILE /opt/freeswitch/var/log/freeswitch/ > /dev/null 2>&1 + tar rf $TMP_LOG_FILE /var/log/nginx/error.log* > /dev/null 2>&1 + tar rf $TMP_LOG_FILE /var/log/nginx/bigbluebutton.access.log* > /dev/null 2>&1 + tar rfh $TMP_LOG_FILE /opt/freeswitch/var/log/freeswitch/ > /dev/null 2>&1 if [ -f /var/log/nginx/html5-client.log ]; then tar rf $TMP_LOG_FILE /var/log/nginx/html5-client.log* > /dev/null 2>&1 @@ -1995,16 +1996,17 @@ if [ -n "$HOST" ]; then #fi fi - ESL_PASSWORD=$(xmlstarlet sel -t -m 'configuration/settings/param[@name="password"]' -v @value /opt/freeswitch/etc/freeswitch/autoload_configs/event_socket.conf.xml) + # + # Update ESL passwords in three configuration files + # + ESL_PASSWORD=$(cat /usr/share/bbb-fsesl-akka/conf/application.conf | grep password | head -n 1 | sed 's/.*="//g' | sed 's/"//g') if [ "$ESL_PASSWORD" == "ClueCon" ]; then ESL_PASSWORD=$(openssl rand -hex 8) - echo "Changing default password for FreeSWITCH Event Socket Layer (see /opt/freeswitch/etc/freeswitch/autoload_configs/event_socket.conf.xml)" + sudo sed -i "s/ClueCon/$ESL_PASSWORD/g" /usr/share/bbb-fsesl-akka/conf/application.conf fi - # Update all references to ESL password - sudo sed -i "s/ClueCon/$ESL_PASSWORD/g" /opt/freeswitch/etc/freeswitch/autoload_configs/event_socket.conf.xml - sudo sed -i "s/ClueCon/$ESL_PASSWORD/g" /usr/share/bbb-fsesl-akka/conf/application.conf sudo yq w -i /usr/local/bigbluebutton/bbb-webrtc-sfu/config/default.yml freeswitch.esl_password "$ESL_PASSWORD" + sudo xmlstarlet edit --inplace --update 'configuration/settings//param[@name="password"]/@value' --value $ESL_PASSWORD /opt/freeswitch/etc/freeswitch/autoload_configs/event_socket.conf.xml echo "Restarting the BigBlueButton $BIGBLUEBUTTON_RELEASE ..." diff --git a/bigbluebutton-config/bin/bbb-record b/bigbluebutton-config/bin/bbb-record index 6cf80ea5bf8bccb561cdd26d32dc2a45c7a43a70..0a2aa60001c042b865b207f15430dc0213fad73c 100755 --- a/bigbluebutton-config/bin/bbb-record +++ b/bigbluebutton-config/bin/bbb-record @@ -388,6 +388,7 @@ if [ $DELETE ]; then rm -rf /usr/share/red5/webapps/video-broadcast/streams/$MEETING_ID rm -f /var/bigbluebutton/screenshare/$MEETING_ID*.flv rm -f /var/freeswitch/meetings/$MEETING_ID*.wav + rm -f /var/freeswitch/meetings/$MEETING_ID*.opus rm -rf /var/bigbluebutton/$MEETING_ID fi @@ -419,6 +420,7 @@ if [ $DELETEALL ]; then find /usr/share/red5/webapps/video-broadcast/streams -name "*.flv" -exec rm '{}' \; rm -f /var/bigbluebutton/screenshare/*.flv rm -f /var/freeswitch/meetings/*.wav + rm -f /var/freeswitch/meetings/*.opus for meeting in $(ls /var/bigbluebutton | grep "^[0-9a-f]\{40\}-[[:digit:]]\{13\}$"); do echo "deleting: $meeting" diff --git a/bigbluebutton-html5/client/main.html b/bigbluebutton-html5/client/main.html index fe5209cf13a23c11cddf8928c3f726afd6fbe1eb..f494c9d887fd126452f8f29936130ad18b257168 100755 --- a/bigbluebutton-html5/client/main.html +++ b/bigbluebutton-html5/client/main.html @@ -56,6 +56,21 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. border: 0; } + .set-z-index { + z-index: 15; + width: 100% !important; + height: 100% !important; + } + + .remove-z-index { + z-index: 0; + } + /* .full-screen { + height: 100% !important; + width: 100% !important; + transform: translateX(0) translateY(0) translateZ(0) !important; + } */ + [hidden]:not([hidden="false"]) { display: none !important; } @@ -73,6 +88,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. </head> <body style="background-color: #06172A"> <div id="app" role="document"></div> + <span id="destination"></span> <audio id="remote-media" autoPlay="autoplay"> <track kind="captions" /> {/* These captions are brought to you by eslint */} </audio> diff --git a/bigbluebutton-html5/imports/api/annotations/addAnnotation.js b/bigbluebutton-html5/imports/api/annotations/addAnnotation.js index 1f1a07dda4b77682535da8582e87dc9b28b16f49..90435ccccbd4d80eb80f7ce7c0bcfa00c4ab7fd4 100755 --- a/bigbluebutton-html5/imports/api/annotations/addAnnotation.js +++ b/bigbluebutton-html5/imports/api/annotations/addAnnotation.js @@ -41,8 +41,10 @@ function handleTextUpdate(meetingId, whiteboardId, userId, annotation) { id, status, annotationType, annotationInfo, wbId, position, } = annotation; - const { textBoxWidth, textBoxHeight } = annotationInfo; - const useDefaultSize = textBoxWidth === 0 && textBoxHeight === 0; + const { textBoxWidth, textBoxHeight, calcedFontSize } = annotationInfo; + const useDefaultSize = (textBoxWidth === 0 && textBoxHeight === 0) + || textBoxWidth < calcedFontSize + || textBoxHeight < calcedFontSize; if (useDefaultSize) { annotationInfo.textBoxWidth = DEFAULT_TEXT_WIDTH; diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js b/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js index f999150b8b0bac5b2b6f857bc227d3a1babdefb6..9f4a820dea477c5ba64af9e2130a870e072ed017 100755 --- a/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js +++ b/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js @@ -8,7 +8,7 @@ const SFU_URL = Meteor.settings.public.kurento.wsUrl; const MEDIA = Meteor.settings.public.media; const MEDIA_TAG = MEDIA.mediaTag.replace(/#/g, ''); const GLOBAL_AUDIO_PREFIX = 'GLOBAL_AUDIO_'; -const RECONNECT_TIMEOUT_MS = 15000; +const RECONNECT_TIMEOUT_MS = MEDIA.listenOnlyCallTimeout || 15000; export default class KurentoAudioBridge extends BaseAudioBridge { constructor(userData) { diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js index 519b8eb2b616da5fddb8297150e6a6335e5c77d9..edf50f7843810eaa6df601eab16ef17aed0c1507 100755 --- a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js +++ b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js @@ -233,6 +233,7 @@ class SIPSession { const { callerIdName, + sessionToken, } = this.user; // WebView safari needs a transceiver to be added. Made it a SIP.js hack. @@ -272,10 +273,11 @@ class SIPSession { }; let userAgentConnected = false; + const token = `sessionToken=${sessionToken}`; this.userAgent = new window.SIP.UA({ uri: `sip:${encodeURIComponent(callerIdName)}@${hostname}`, - wsServers: `${(protocol === 'https:' ? 'wss://' : 'ws://')}${hostname}/ws`, + wsServers: `${(protocol === 'https:' ? 'wss://' : 'ws://')}${hostname}/ws?${token}`, displayName: callerIdName, register: false, traceSip: true, diff --git a/bigbluebutton-html5/imports/api/breakouts/server/methods/createBreakout.js b/bigbluebutton-html5/imports/api/breakouts/server/methods/createBreakout.js index 2b069675ef3fdfc0b0a3e3340998071f89152f45..315ce25e254efa16253cc0a873eedb37d9efe99a 100644 --- a/bigbluebutton-html5/imports/api/breakouts/server/methods/createBreakout.js +++ b/bigbluebutton-html5/imports/api/breakouts/server/methods/createBreakout.js @@ -6,11 +6,14 @@ import { extractCredentials } from '/imports/api/common/server/helpers'; export default function createBreakoutRoom(rooms, durationInMinutes, record = false) { const REDIS_CONFIG = Meteor.settings.private.redis; const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; + const BREAKOUT_LIM = Meteor.settings.public.app.breakoutRoomLimit; + const MIN_BREAKOUT_ROOMS = 2; + const MAX_BREAKOUT_ROOMS = BREAKOUT_LIM > MIN_BREAKOUT_ROOMS ? BREAKOUT_LIM : MIN_BREAKOUT_ROOMS; const { meetingId, requesterUserId } = extractCredentials(this.userId); const eventName = 'CreateBreakoutRoomsCmdMsg'; - if (rooms.length > 8) return Logger.info(`Attempt to create breakout rooms with invalid number of rooms in meeting id=${meetingId}`); + if (rooms.length > MAX_BREAKOUT_ROOMS) return Logger.info(`Attempt to create breakout rooms with invalid number of rooms in meeting id=${meetingId}`); const payload = { record, durationInMinutes, diff --git a/bigbluebutton-html5/imports/api/external-videos/server/methods/startWatchingExternalVideo.js b/bigbluebutton-html5/imports/api/external-videos/server/methods/startWatchingExternalVideo.js index e157b7a0b3e0ede194b9d16fa76152a59633bc1b..df2b88abe3fc2d0ee78002adc9cc560aa6d15388 100644 --- a/bigbluebutton-html5/imports/api/external-videos/server/methods/startWatchingExternalVideo.js +++ b/bigbluebutton-html5/imports/api/external-videos/server/methods/startWatchingExternalVideo.js @@ -2,6 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import Logger from '/imports/startup/server/logger'; import Meetings from '/imports/api/meetings'; +import Users from '/imports/api/users'; import RedisPubSub from '/imports/startup/server/redis'; import { extractCredentials } from '/imports/api/common/server/helpers'; @@ -10,16 +11,29 @@ export default function startWatchingExternalVideo(options) { const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; const EVENT_NAME = 'StartExternalVideoMsg'; - const { meetingId, requesterUserId } = extractCredentials(this.userId); + const { meetingId, requesterUserId: userId } = extractCredentials(this.userId); const { externalVideoUrl } = options; - check(externalVideoUrl, String); + try { + check(meetingId, String); + check(userId, String); + check(externalVideoUrl, String); - Meetings.update({ meetingId }, { $set: { externalVideoUrl } }); + const user = Users.findOne({ meetingId, userId, presenter: true }, { presenter: 1 }); - const payload = { externalVideoUrl }; + if (!user) { + Logger.error(`Only presenters are allowed to start external video for a meeting. meeting=${meetingId} userId=${userId}`); + return; + } - Logger.info(`User id=${requesterUserId} sharing an external video: ${externalVideoUrl} for meeting ${meetingId}`); + Meetings.update({ meetingId }, { $set: { externalVideoUrl } }); - return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); + const payload = { externalVideoUrl }; + + Logger.info(`User id=${userId} sharing an external video: ${externalVideoUrl} for meeting ${meetingId}`); + + return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, userId, payload); + } catch (error) { + Logger.error(`Error on sharing an external video: ${externalVideoUrl} ${error}`); + } } diff --git a/bigbluebutton-html5/imports/api/external-videos/server/methods/stopWatchingExternalVideo.js b/bigbluebutton-html5/imports/api/external-videos/server/methods/stopWatchingExternalVideo.js index 60384b82f772230d68d5c5399008f7de9e780610..c1fe37b6c7378c69f1bd747e6a28c4a8326e4d59 100644 --- a/bigbluebutton-html5/imports/api/external-videos/server/methods/stopWatchingExternalVideo.js +++ b/bigbluebutton-html5/imports/api/external-videos/server/methods/stopWatchingExternalVideo.js @@ -1,6 +1,7 @@ import { Meteor } from 'meteor/meteor'; import Logger from '/imports/startup/server/logger'; import Meetings from '/imports/api/meetings'; +import Users from '/imports/api/users'; import RedisPubSub from '/imports/startup/server/redis'; import { extractCredentials } from '/imports/api/common/server/helpers'; @@ -9,19 +10,33 @@ export default function stopWatchingExternalVideo(options) { const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; const EVENT_NAME = 'StopExternalVideoMsg'; - if (this.userId) { - options = extractCredentials(this.userId); - } + const { meetingId, requesterUserId } = this.userId ? extractCredentials(this.userId) : options; + + try { + check(meetingId, String); + check(requesterUserId, String); - const { meetingId, requesterUserId } = options; + const user = Users.findOne({ + meetingId, + userId: requesterUserId, + presenter: true, + }, { presenter: 1 }); - const meeting = Meetings.findOne({ meetingId }); - if (!meeting || meeting.externalVideoUrl === null) return; + if (this.userId && !user) { + Logger.error(`Only presenters are allowed to stop external video for a meeting. meeting=${meetingId} userId=${requesterUserId}`); + return; + } - Meetings.update({ meetingId }, { $set: { externalVideoUrl: null } }); - const payload = {}; + const meeting = Meetings.findOne({ meetingId }); + if (!meeting || meeting.externalVideoUrl === null) return; - Logger.info(`User id=${requesterUserId} stopped sharing an external video for meeting=${meetingId}`); + Meetings.update({ meetingId }, { $set: { externalVideoUrl: null } }); + const payload = {}; - RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); + Logger.info(`User id=${requesterUserId} stopped sharing an external video for meeting=${meetingId}`); + + RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); + } catch (error) { + Logger.error(`Error on stop sharing an external video for meeting=${meetingId} ${error}`); + } } diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/clearGroupChatMsg.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/clearGroupChatMsg.js index 3ae6f31d298e1a486eb914a2dc0dd663e64ca479..f9cdd35b9e28cdbf5e9221719c4021ce179aed2e 100644 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/clearGroupChatMsg.js +++ b/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/clearGroupChatMsg.js @@ -27,7 +27,7 @@ export default function clearGroupChatMsg(meetingId, chatId) { } if (meetingId) { - return GroupChatMsg.remove({ meetingId, chatId: { $eq: PUBLIC_GROUP_CHAT_ID } }, () => { + return GroupChatMsg.remove({ meetingId }, () => { Logger.info(`Cleared GroupChatMsg (${meetingId})`); }); } diff --git a/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js b/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js index 6ffdb31ca1d46529e6bc8f4b88fe1fa9f803d251..4dce903de39c43adc4500275f40f8e11b9bb34d1 100755 --- a/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js +++ b/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js @@ -3,6 +3,7 @@ import { check, Match, } from 'meteor/check'; +import SanitizeHTML from 'sanitize-html'; import Meetings, { RecordMeetings } from '/imports/api/meetings'; import Logger from '/imports/startup/server/logger'; import createNote from '/imports/api/note/server/methods/createNote'; @@ -41,8 +42,6 @@ export default function addMeeting(meeting) { createdTime: Number, duration: Number, createdDate: String, - maxInactivityTimeoutMinutes: Number, - warnMinutesBeforeMax: Number, meetingExpireIfNoUserJoinedInMinutes: Number, meetingExpireWhenLastUserLeftInMinutes: Number, userInactivityInspectTimerInMinutes: Number, @@ -104,22 +103,41 @@ export default function addMeeting(meeting) { const meetingEnded = false; - newMeeting.welcomeProp.welcomeMsg = newMeeting.welcomeProp.welcomeMsg.replace( + let { welcomeMsg } = newMeeting.welcomeProp; + + const sanitizeTextInChat = original => SanitizeHTML(original, { + allowedTags: ['a', 'b', 'br', 'i', 'img', 'li', 'small', 'span', 'strong', 'u', 'ul'], + allowedAttributes: { + a: ['href', 'name', 'target'], + img: ['src', 'width', 'height'], + }, + allowedSchemes: ['https'], + }); + + const sanitizedWelcomeText = sanitizeTextInChat(welcomeMsg); + welcomeMsg = sanitizedWelcomeText.replace( 'href="event:', 'href="', ); const insertBlankTarget = (s, i) => `${s.substr(0, i)} target="_blank"${s.substr(i)}`; const linkWithoutTarget = new RegExp('<a href="(.*?)">', 'g'); - linkWithoutTarget.test(newMeeting.welcomeProp.welcomeMsg); + linkWithoutTarget.test(welcomeMsg); if (linkWithoutTarget.lastIndex > 0) { - newMeeting.welcomeProp.welcomeMsg = insertBlankTarget( - newMeeting.welcomeProp.welcomeMsg, + welcomeMsg = insertBlankTarget( + welcomeMsg, linkWithoutTarget.lastIndex - 1, ); } + newMeeting.welcomeProp.welcomeMsg = welcomeMsg; + + // note: as of July 2020 `modOnlyMessage` is not published to the client side. + // We are sanitizing this data simply to prevent future potential usage + // At the moment `modOnlyMessage` is obtained from client side as a response to Enter API + newMeeting.welcomeProp.modOnlyMessage = sanitizeTextInChat(newMeeting.welcomeProp.modOnlyMessage); + const modifier = { $set: Object.assign({ meetingId, diff --git a/bigbluebutton-html5/imports/api/meetings/server/modifiers/meetingHasEnded.js b/bigbluebutton-html5/imports/api/meetings/server/modifiers/meetingHasEnded.js index 3b9efcbb1698899b0e0347f2f196d546067e882a..acc6f5b47255a5509170be64a5b215af98b187ca 100755 --- a/bigbluebutton-html5/imports/api/meetings/server/modifiers/meetingHasEnded.js +++ b/bigbluebutton-html5/imports/api/meetings/server/modifiers/meetingHasEnded.js @@ -22,6 +22,7 @@ import clearLocalSettings from '/imports/api/local-settings/server/modifiers/cle import clearRecordMeeting from './clearRecordMeeting'; import clearVoiceCallStates from '/imports/api/voice-call-states/server/modifiers/clearVoiceCallStates'; import clearVideoStreams from '/imports/api/video-streams/server/modifiers/clearVideoStreams'; +import BannedUsers from '/imports/api/users/server/store/bannedUsers'; export default function meetingHasEnded(meetingId) { removeAnnotationsStreamer(meetingId); @@ -46,6 +47,7 @@ export default function meetingHasEnded(meetingId) { clearRecordMeeting(meetingId); clearVoiceCallStates(meetingId); clearVideoStreams(meetingId); + BannedUsers.delete(meetingId); return Logger.info(`Cleared Meetings with id ${meetingId}`); }); diff --git a/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js b/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js index 761e383ede9eafae27c72990146aaadfc41a26e2..d3ffdff3b8e00d297ec3c56c6a65360f9b843482 100755 --- a/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js +++ b/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js @@ -63,15 +63,80 @@ export default class KurentoScreenshareBridge { return normalizedError; } - async kurentoWatchVideo() { + static playElement(screenshareMediaElement) { + const mediaTagPlayed = () => { + logger.info({ + logCode: 'screenshare_media_play_success', + }, 'Screenshare media played successfully'); + }; + + if (screenshareMediaElement.paused) { + // Tag isn't playing yet. Play it. + screenshareMediaElement.play() + .then(mediaTagPlayed) + .catch((error) => { + // NotAllowedError equals autoplay issues, fire autoplay handling event. + // This will be handled in the screenshare react component. + if (error.name === 'NotAllowedError') { + logger.error({ + logCode: 'screenshare_error_autoplay', + extraInfo: { errorName: error.name }, + }, 'Screenshare play failed due to autoplay error'); + const tagFailedEvent = new CustomEvent('screensharePlayFailed', + { detail: { mediaElement: screenshareMediaElement } }); + window.dispatchEvent(tagFailedEvent); + } else { + // Tag failed for reasons other than autoplay. Log the error and + // try playing again a few times until it works or fails for good + const played = playAndRetry(screenshareMediaElement); + if (!played) { + logger.error({ + logCode: 'screenshare_error_media_play_failed', + extraInfo: { errorName: error.name }, + }, `Screenshare media play failed due to ${error.name}`); + } else { + mediaTagPlayed(); + } + } + }); + } else { + // Media tag is already playing, so log a success. This is really a + // logging fallback for a case that shouldn't happen. But if it does + // (ie someone re-enables the autoPlay prop in the element), then it + // means the stream is playing properly and it'll be logged. + mediaTagPlayed(); + } + } + + static screenshareElementLoadAndPlay(stream, element, muted) { + element.muted = muted; + element.pause(); + element.srcObject = stream; + KurentoScreenshareBridge.playElement(element); + } + + kurentoViewLocalPreview() { + const screenshareMediaElement = document.getElementById(SCREENSHARE_VIDEO_TAG); + const { webRtcPeer } = window.kurentoManager.kurentoScreenshare; + + if (webRtcPeer) { + const stream = webRtcPeer.getLocalStream(); + KurentoScreenshareBridge.screenshareElementLoadAndPlay(stream, screenshareMediaElement, true); + } + } + + async kurentoViewScreen() { + const screenshareMediaElement = document.getElementById(SCREENSHARE_VIDEO_TAG); let iceServers = []; let started = false; try { iceServers = await fetchWebRTCMappedStunTurnServers(getSessionToken()); } catch (error) { - logger.error({ logCode: 'screenshare_viwer_fetchstunturninfo_error', extraInfo: { error } }, - 'Screenshare bridge failed to fetch STUN/TURN info, using default'); + logger.error({ + logCode: 'screenshare_viewer_fetchstunturninfo_error', + extraInfo: { error }, + }, 'Screenshare bridge failed to fetch STUN/TURN info, using default'); iceServers = getMappedFallbackStun(); } finally { const options = { @@ -81,52 +146,6 @@ export default class KurentoScreenshareBridge { userName: getUsername(), }; - const screenshareTag = document.getElementById(SCREENSHARE_VIDEO_TAG); - - const playElement = () => { - const mediaTagPlayed = () => { - logger.info({ - logCode: 'screenshare_viewer_media_play_success', - }, 'Screenshare viewer media played successfully'); - }; - if (screenshareTag.paused) { - // Tag isn't playing yet. Play it. - screenshareTag.play() - .then(mediaTagPlayed) - .catch((error) => { - // NotAllowedError equals autoplay issues, fire autoplay handling event. - // This will be handled in the screenshare react component. - if (error.name === 'NotAllowedError') { - logger.error({ - logCode: 'screenshare_viewer_error_autoplay', - extraInfo: { errorName: error.name }, - }, 'Screenshare viewer play failed due to autoplay error'); - const tagFailedEvent = new CustomEvent('screensharePlayFailed', - { detail: { mediaElement: screenshareTag } }); - window.dispatchEvent(tagFailedEvent); - } else { - // Tag failed for reasons other than autoplay. Log the error and - // try playing again a few times until it works or fails for good - const played = playAndRetry(screenshareTag); - if (!played) { - logger.error({ - logCode: 'screenshare_viewer_error_media_play_failed', - extraInfo: { errorName: error.name }, - }, `Screenshare viewer media play failed due to ${error.name}`); - } else { - mediaTagPlayed(); - } - } - }); - } else { - // Media tag is already playing, so log a success. This is really a - // logging fallback for a case that shouldn't happen. But if it does - // (ie someone re-enables the autoPlay prop in the element), then it - // means the stream is playing properly and it'll be logged. - mediaTagPlayed(); - } - }; - const onFail = (error) => { KurentoScreenshareBridge.handleViewerFailure(error, started); }; @@ -139,10 +158,11 @@ export default class KurentoScreenshareBridge { const { webRtcPeer } = window.kurentoManager.kurentoVideo; if (webRtcPeer) { const stream = webRtcPeer.getRemoteStream(); - screenshareTag.muted = true; - screenshareTag.pause(); - screenshareTag.srcObject = stream; - playElement(); + KurentoScreenshareBridge.screenshareElementLoadAndPlay( + stream, + screenshareMediaElement, + true, + ); } }; diff --git a/bigbluebutton-html5/imports/api/users-settings/server/methods/addUserSettings.js b/bigbluebutton-html5/imports/api/users-settings/server/methods/addUserSettings.js index c28ed339694dfd9ee9529992d0a8f44365a59e16..93719cad394604c4aedde1d35dbff120faba0abe 100644 --- a/bigbluebutton-html5/imports/api/users-settings/server/methods/addUserSettings.js +++ b/bigbluebutton-html5/imports/api/users-settings/server/methods/addUserSettings.js @@ -31,6 +31,7 @@ const oldParametersKeys = Object.keys(oldParameters); const currentParameters = [ // APP 'bbb_ask_for_feedback_on_logout', + 'bbb_override_default_locale', 'bbb_auto_join_audio', 'bbb_client_title', 'bbb_force_listen_only', @@ -45,6 +46,7 @@ const currentParameters = [ 'bbb_preferred_camera_profile', 'bbb_enable_screen_sharing', 'bbb_enable_video', + 'bbb_record_video', 'bbb_skip_video_preview', 'bbb_mirror_own_webcam', // PRESENTATION @@ -68,10 +70,10 @@ const currentParameters = [ function valueParser(val) { try { - const parsedValue = JSON.parse(val.toLowerCase()); + const parsedValue = JSON.parse(val.toLowerCase().trim()); return parsedValue; } catch (error) { - logger.error('Parameter value could not ber parsed'); + logger.warn(`addUserSettings:Parameter ${val} could not be parsed (was not json)`); return val; } } @@ -85,21 +87,22 @@ export default function addUserSettings(settings) { settings.forEach((el) => { const settingKey = Object.keys(el).shift(); + const normalizedKey = settingKey.trim(); - if (currentParameters.includes(settingKey)) { - if (!Object.keys(parameters).includes(settingKey)) { + if (currentParameters.includes(normalizedKey)) { + if (!Object.keys(parameters).includes(normalizedKey)) { parameters = { - [settingKey]: valueParser(el[settingKey]), + [normalizedKey]: valueParser(el[settingKey]), ...parameters, }; } else { - parameters[settingKey] = el[settingKey]; + parameters[normalizedKey] = el[settingKey]; } return; } - if (oldParametersKeys.includes(settingKey)) { - const matchingNewKey = oldParameters[settingKey]; + if (oldParametersKeys.includes(normalizedKey)) { + const matchingNewKey = oldParameters[normalizedKey]; if (!Object.keys(parameters).includes(matchingNewKey)) { parameters = { [matchingNewKey]: valueParser(el[settingKey]), @@ -109,7 +112,7 @@ export default function addUserSettings(settings) { return; } - logger.warn(`Parameter ${settingKey} not handled`); + logger.warn(`Parameter ${normalizedKey} not handled`); }); const settingsAdded = []; diff --git a/bigbluebutton-html5/imports/api/users-settings/server/publishers.js b/bigbluebutton-html5/imports/api/users-settings/server/publishers.js index 0b772099b42c8eb1007a2387a14a0d1d747f4194..a0b88c4e5d3a191ac482b10d7a8b0a127ef1e139 100644 --- a/bigbluebutton-html5/imports/api/users-settings/server/publishers.js +++ b/bigbluebutton-html5/imports/api/users-settings/server/publishers.js @@ -2,6 +2,7 @@ import { Meteor } from 'meteor/meteor'; import UserSettings from '/imports/api/users-settings'; import Logger from '/imports/startup/server/logger'; import { extractCredentials } from '/imports/api/common/server/helpers'; +import User from '/imports/api/users'; function userSettings() { if (!this.userId) { @@ -9,6 +10,34 @@ function userSettings() { } const { meetingId, requesterUserId } = extractCredentials(this.userId); + const currentUser = User.findOne({ userId: requesterUserId }); + + if (currentUser && currentUser.breakoutProps.isBreakoutUser) { + const { parentId } = currentUser.breakoutProps; + + const [externalId] = currentUser.extId.split('-'); + + const mainRoomUserSettings = UserSettings.find({ meetingId: parentId, userId: externalId }); + + mainRoomUserSettings.map(({ setting, value }) => ({ + meetingId, + setting, + userId: requesterUserId, + value, + })).forEach((doc) => { + const selector = { + meetingId, + setting: doc.setting, + }; + + UserSettings.upsert(selector, doc); + }); + + Logger.debug(`Publishing user settings for user=${requesterUserId}`); + + return UserSettings.find({ meetingId, userId: requesterUserId }); + } + Logger.debug(`Publishing user settings for user=${requesterUserId}`); return UserSettings.find({ meetingId, userId: requesterUserId }); diff --git a/bigbluebutton-html5/imports/api/users/server/modifiers/addUser.js b/bigbluebutton-html5/imports/api/users/server/modifiers/addUser.js index b5d359fecbf22fa5ca41c42918815704ee0f1f8d..a7606514f1f2711006ad369d32d60dadd60417e3 100755 --- a/bigbluebutton-html5/imports/api/users/server/modifiers/addUser.js +++ b/bigbluebutton-html5/imports/api/users/server/modifiers/addUser.js @@ -3,6 +3,8 @@ import Logger from '/imports/startup/server/logger'; import Users from '/imports/api/users'; import Meetings from '/imports/api/meetings'; import VoiceUsers from '/imports/api/voice-users/'; +import _ from 'lodash'; +import SanitizeHTML from 'sanitize-html'; import stringHash from 'string-hash'; import flat from 'flat'; @@ -15,7 +17,17 @@ const COLOR_LIST = [ '#0d47a1', '#0277bd', '#01579b', ]; -export default function addUser(meetingId, user) { +export default function addUser(meetingId, userData) { + const user = userData; + const sanitizedName = SanitizeHTML(userData.name, { + allowedTags: [], + allowedAttributes: {}, + }); + // if user typed only tags + user.name = sanitizedName.length === 0 + ? _.escape(userData.name) + : sanitizedName; + check(meetingId, String); check(user, { diff --git a/bigbluebutton-html5/imports/api/users/server/store/bannedUsers.js b/bigbluebutton-html5/imports/api/users/server/store/bannedUsers.js index 5355e3d1dcec3bf441259010f1d0ac484201cef3..f731bedf2104bdaeb17a9bd148516f15ce2b455e 100644 --- a/bigbluebutton-html5/imports/api/users/server/store/bannedUsers.js +++ b/bigbluebutton-html5/imports/api/users/server/store/bannedUsers.js @@ -7,7 +7,7 @@ class BannedUsers { } init(meetingId) { - Logger.debug('BannedUsers :: init', meetingId); + Logger.debug('BannedUsers :: init', { meetingId }); if (!this.store[meetingId]) this.store[meetingId] = new Set(); } @@ -20,7 +20,7 @@ class BannedUsers { } delete(meetingId) { - Logger.debug('BannedUsers :: delete', meetingId); + Logger.debug('BannedUsers :: delete', { meetingId }); delete this.store[meetingId]; } diff --git a/bigbluebutton-html5/imports/startup/client/base.jsx b/bigbluebutton-html5/imports/startup/client/base.jsx index 040ea81e531f9e111bab4e3b1b9cb4beb83e0e57..5ee1d8e6e4c93356a5993eb30de52107f5ddfde7 100755 --- a/bigbluebutton-html5/imports/startup/client/base.jsx +++ b/bigbluebutton-html5/imports/startup/client/base.jsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { Component, Fragment } from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import PropTypes from 'prop-types'; import Auth from '/imports/ui/services/auth'; @@ -18,6 +18,9 @@ import AudioService from '/imports/ui/components/audio/service'; import { notify } from '/imports/ui/services/notification'; import deviceInfo from '/imports/utils/deviceInfo'; import getFromUserSettings from '/imports/ui/services/users-settings'; +import LayoutManager from '/imports/ui/components/layout/layout-manager'; +import { withLayoutContext } from '/imports/ui/components/layout/context'; +import VideoService from '/imports/ui/components/video-provider/service'; const CHAT_CONFIG = Meteor.settings.public.chat; const CHAT_ENABLED = CHAT_CONFIG.enabled; @@ -101,12 +104,23 @@ class Base extends Component { ejected, isMeteorConnected, subscriptionsReady, + layoutContextDispatch, + usersVideo, } = this.props; const { loading, meetingExisted, } = this.state; + if (usersVideo !== prevProps.usersVideo) { + layoutContextDispatch( + { + type: 'setUsersVideo', + value: usersVideo.length, + }, + ); + } + if (!prevProps.subscriptionsReady && subscriptionsReady) { logger.info({ logCode: 'startup_client_subscriptions_ready' }, 'Subscriptions are ready'); } @@ -170,6 +184,7 @@ class Base extends Component { const { codeError, ejected, + ejectedReason, meetingExist, meetingHasEnded, meetingIsBreakout, @@ -182,22 +197,21 @@ class Base extends Component { } if (ejected) { - return (<MeetingEnded code="403" />); + return (<MeetingEnded code="403" reason={ejectedReason} />); } - if ((meetingHasEnded || User.loggedOut) && meetingIsBreakout) { + if ((meetingHasEnded || User?.loggedOut) && meetingIsBreakout) { window.close(); return null; } - if (((meetingHasEnded && !meetingIsBreakout)) || (codeError && (User && User.loggedOut))) { + if (((meetingHasEnded && !meetingIsBreakout)) || (codeError && User?.loggedOut)) { return (<MeetingEnded code={codeError} />); } if (codeError && !meetingHasEnded) { // 680 is set for the codeError when the user requests a logout if (codeError !== '680') { - logger.error({ logCode: 'startup_client_usercouldnotlogin_error' }, `User could not log in HTML5, hit ${codeError}`); return (<ErrorScreen code={codeError} />); } return (<MeetingEnded code={codeError} />); @@ -207,13 +221,20 @@ class Base extends Component { } render() { - const { meetingExist } = this.props; + const { + meetingExist, + } = this.props; const { meetingExisted } = this.state; return ( - (!meetingExisted && !meetingExist && Auth.loggedIn) - ? <LoadingScreen /> - : this.renderByState() + <Fragment> + <LayoutManager /> + { + (!meetingExisted && !meetingExist && Auth.loggedIn) + ? <LoadingScreen /> + : this.renderByState() + } + </Fragment> ); } } @@ -242,6 +263,7 @@ const BaseContainer = withTracker(() => { approved: 1, authed: 1, ejected: 1, + ejectedReason: 1, color: 1, effectiveConnectionType: 1, extId: 1, @@ -251,6 +273,8 @@ const BaseContainer = withTracker(() => { loggedOut: 1, meetingId: 1, userId: 1, + inactivityCheck: 1, + responseDelay: 1, }; const User = Users.findOne({ intId: credentials.requesterUserId }, { fields }); const meeting = Meetings.findOne({ meetingId }, { @@ -264,8 +288,10 @@ const BaseContainer = withTracker(() => { Session.set('codeError', '410'); } - const approved = User && User.approved && User.guest; - const ejected = User && User.ejected; + const approved = User?.approved && User?.guest; + const ejected = User?.ejected; + const ejectedReason = User?.ejectedReason; + let userSubscriptionHandler; Breakouts.find({}, { fields: { _id: 1 } }).observeChanges({ @@ -366,10 +392,12 @@ const BaseContainer = withTracker(() => { } const codeError = Session.get('codeError'); + const usersVideo = VideoService.getVideoStreams(); return { approved, ejected, + ejectedReason, userSubscriptionHandler, breakoutRoomSubscriptionHandler, meetingModeratorSubscriptionHandler, @@ -382,7 +410,8 @@ const BaseContainer = withTracker(() => { subscriptionsReady: Session.get('subscriptionsReady'), loggedIn, codeError, + usersVideo, }; -})(Base); +})(withLayoutContext(Base)); export default BaseContainer; diff --git a/bigbluebutton-html5/imports/startup/client/intl.jsx b/bigbluebutton-html5/imports/startup/client/intl.jsx index 3e139e57c74e1a80cd498828926f1889893de47e..88f6292deba4dc66e3729dc5b3a477756f9c9c75 100644 --- a/bigbluebutton-html5/imports/startup/client/intl.jsx +++ b/bigbluebutton-html5/imports/startup/client/intl.jsx @@ -1,112 +1,17 @@ import React, { Component } from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import PropTypes from 'prop-types'; -import { IntlProvider, addLocaleData } from 'react-intl'; +import { IntlProvider } from 'react-intl'; import Settings from '/imports/ui/services/settings'; import LoadingScreen from '/imports/ui/components/loading-screen/component'; - -// currently supported locales. -import ar from 'react-intl/locale-data/ar'; -import az from 'react-intl/locale-data/az'; -import bg from 'react-intl/locale-data/bg'; -import ca from 'react-intl/locale-data/ca'; -import cs from 'react-intl/locale-data/cs'; -import da from 'react-intl/locale-data/da'; -import de from 'react-intl/locale-data/de'; -import el from 'react-intl/locale-data/el'; -import en from 'react-intl/locale-data/en'; -import es from 'react-intl/locale-data/es'; -import et from 'react-intl/locale-data/et'; -import eu from 'react-intl/locale-data/eu'; -import fa from 'react-intl/locale-data/fa'; -import fi from 'react-intl/locale-data/fi'; -import fr from 'react-intl/locale-data/fr'; -import gl from 'react-intl/locale-data/gl'; -import he from 'react-intl/locale-data/he'; -import hi from 'react-intl/locale-data/hi'; -import hr from 'react-intl/locale-data/hr'; -import hu from 'react-intl/locale-data/hu'; -import hy from 'react-intl/locale-data/hy'; -import id from 'react-intl/locale-data/id'; -import it from 'react-intl/locale-data/it'; -import ja from 'react-intl/locale-data/ja'; -import ka from 'react-intl/locale-data/ka'; -import km from 'react-intl/locale-data/km'; -import kn from 'react-intl/locale-data/kn'; -import ko from 'react-intl/locale-data/ko'; -import lt from 'react-intl/locale-data/lt'; -import lv from 'react-intl/locale-data/lv'; -import nb from 'react-intl/locale-data/nb'; -import nl from 'react-intl/locale-data/nl'; -import pl from 'react-intl/locale-data/pl'; -import pt from 'react-intl/locale-data/pt'; -import ro from 'react-intl/locale-data/ro'; -import ru from 'react-intl/locale-data/ru'; -import sk from 'react-intl/locale-data/sk'; -import sl from 'react-intl/locale-data/sl'; -import sr from 'react-intl/locale-data/sr'; -import sv from 'react-intl/locale-data/sv'; -import th from 'react-intl/locale-data/th'; -import tr from 'react-intl/locale-data/tr'; -import uk from 'react-intl/locale-data/uk'; -import vi from 'react-intl/locale-data/vi'; -import zh from 'react-intl/locale-data/zh'; - - -addLocaleData([ - ...ar, - ...az, - ...bg, - ...ca, - ...cs, - ...da, - ...de, - ...el, - ...et, - ...en, - ...es, - ...eu, - ...fa, - ...fi, - ...fr, - ...gl, - ...he, - ...hi, - ...hr, - ...hu, - ...hy, - ...id, - ...it, - ...ja, - ...ka, - ...km, - ...kn, - ...ko, - ...lt, - ...lv, - ...nb, - ...nl, - ...pl, - ...pt, - ...ro, - ...ru, - ...sk, - ...sl, - ...sr, - ...sv, - ...th, - ...tr, - ...uk, - ...vi, - ...zh, -]); +import getFromUserSettings from '/imports/ui/services/users-settings'; const propTypes = { locale: PropTypes.string, children: PropTypes.element.isRequired, }; -const DEFAULT_LANGUAGE = Meteor.settings.public.app.defaultSettings.application.locale; +const DEFAULT_LANGUAGE = Meteor.settings.public.app.defaultSettings.application.fallbackLocale; const RTL_LANGUAGES = ['ar', 'he', 'fa']; @@ -115,13 +20,31 @@ const defaultProps = { }; class IntlStartup extends Component { + static saveLocale(localeName) { + if (Settings.application.locale !== localeName) { + Settings.application.changedLocale = localeName; + } + + Settings.application.locale = localeName; + + if (RTL_LANGUAGES.includes(localeName.substring(0, 2))) { + document.body.parentNode.setAttribute('dir', 'rtl'); + Settings.application.isRTL = true; + } else { + document.body.parentNode.setAttribute('dir', 'ltr'); + Settings.application.isRTL = false; + } + Settings.save(); + } + constructor(props) { super(props); this.state = { messages: {}, normalizedLocale: null, - fetching: false, + fetching: true, + localeChanged: false, }; if (RTL_LANGUAGES.includes(props.locale)) { @@ -131,18 +54,39 @@ class IntlStartup extends Component { this.fetchLocalizedMessages = this.fetchLocalizedMessages.bind(this); } - componentWillMount() { + componentDidMount() { const { locale } = this.props; this.fetchLocalizedMessages(locale, true); } - componentWillUpdate(nextProps) { - const { fetching, normalizedLocale } = this.state; - const { locale } = nextProps; + componentDidUpdate(prevProps) { + const { fetching, normalizedLocale, localeChanged } = this.state; + const { locale, overrideLocale, changedLocale } = this.props; + + if (prevProps.locale !== locale) { + this.setState({ + localeChanged: true, + }); + } + + if (overrideLocale) { + if (!fetching + && (overrideLocale !== normalizedLocale.toLowerCase()) + && !localeChanged + && !changedLocale) { + this.fetchLocalizedMessages(overrideLocale); + } + + if (!localeChanged) { + return; + } + } if (!fetching && normalizedLocale - && locale.toLowerCase() !== normalizedLocale.toLowerCase()) { + && ((locale.toLowerCase() !== normalizedLocale.toLowerCase()))) { + if (((DEFAULT_LANGUAGE === normalizedLocale.toLowerCase()) && !localeChanged)) return; + this.fetchLocalizedMessages(locale); } } @@ -162,34 +106,23 @@ class IntlStartup extends Component { .then(({ messages, normalizedLocale }) => { const dasherizedLocale = normalizedLocale.replace('_', '-'); this.setState({ messages, fetching: false, normalizedLocale: dasherizedLocale }, () => { - this.saveLocale(dasherizedLocale); + IntlStartup.saveLocale(dasherizedLocale); }); }) .catch(() => { this.setState({ fetching: false, normalizedLocale: null }, () => { - this.saveLocale(DEFAULT_LANGUAGE); + IntlStartup.saveLocale(DEFAULT_LANGUAGE); }); }); }); } - saveLocale(localeName) { - Settings.application.locale = localeName; - if (RTL_LANGUAGES.includes(localeName.substring(0, 2))) { - document.body.parentNode.setAttribute('dir', 'rtl'); - Settings.application.isRTL = true; - } else { - document.body.parentNode.setAttribute('dir', 'ltr'); - Settings.application.isRTL = false; - } - Settings.save(); - } render() { const { fetching, normalizedLocale, messages } = this.state; const { children } = this.props; - return fetching ? <LoadingScreen /> : ( + return (fetching || !normalizedLocale) ? <LoadingScreen /> : ( <IntlProvider locale={normalizedLocale} messages={messages}> {children} </IntlProvider> @@ -198,10 +131,12 @@ class IntlStartup extends Component { } const IntlStartupContainer = withTracker(() => { - const { locale } = Settings.application; - + const { locale, changedLocale } = Settings.application; + const overrideLocale = getFromUserSettings('bbb_override_default_locale', null); return { locale, + overrideLocale, + changedLocale, }; })(IntlStartup); diff --git a/bigbluebutton-html5/imports/startup/server/index.js b/bigbluebutton-html5/imports/startup/server/index.js index c594a2205a4efb6c1a720c5416c7189332607125..9fb43f66e1b89a8612db47cf65faf34e8a589769 100755 --- a/bigbluebutton-html5/imports/startup/server/index.js +++ b/bigbluebutton-html5/imports/startup/server/index.js @@ -8,12 +8,38 @@ import { lookup as lookupUserAgent } from 'useragent'; import { check } from 'meteor/check'; import Logger from './logger'; import Redis from './redis'; + import setMinBrowserVersions from './minBrowserVersion'; import userLeaving from '/imports/api/users/server/methods/userLeaving'; -const AVAILABLE_LOCALES = fs.readdirSync('assets/app/locales'); let guestWaitHtml = ''; -let avaibleLocalesNames = []; +const AVAILABLE_LOCALES = fs.readdirSync('assets/app/locales'); +const FALLBACK_LOCALES = JSON.parse(Assets.getText('config/fallbackLocales.json')); + +const generateLocaleOptions = () => { + try { + Logger.warn('Calculating aggregateLocales (heavy)'); + const tempAggregateLocales = AVAILABLE_LOCALES + .map(file => file.replace('.json', '')) + .map(file => file.replace('_', '-')) + .map((locale) => { + const localeName = (Langmap[locale] || {}).nativeName + || (FALLBACK_LOCALES[locale] || {}).nativeName + || locale; + return { + locale, + name: localeName, + }; + }); + Logger.warn(`Total locales: ${tempAggregateLocales.length}`, tempAggregateLocales); + return tempAggregateLocales; + } catch (e) { + Logger.error(`'Could not process locales error: ${e}`); + return []; + } +}; + +let avaibleLocalesNamesJSON = JSON.stringify(generateLocaleOptions()); Meteor.startup(() => { const APP_CONFIG = Meteor.settings.public.app; @@ -152,23 +178,13 @@ WebApp.connectHandlers.use('/locale', (req, res) => { }); WebApp.connectHandlers.use('/locales', (req, res) => { - if (!avaibleLocalesNames.length) { - try { - avaibleLocalesNames = AVAILABLE_LOCALES - .map(file => file.replace('.json', '')) - .map(file => file.replace('_', '-')) - .map(locale => ({ - locale, - name: Langmap[locale].nativeName, - })); - } catch (e) { - Logger.warn(`'Could not process locales error: ${e}`); - } + if (!avaibleLocalesNamesJSON) { + avaibleLocalesNamesJSON = JSON.stringify(generateLocaleOptions()); } res.setHeader('Content-Type', 'application/json'); res.writeHead(200); - res.end(JSON.stringify(avaibleLocalesNames)); + res.end(avaibleLocalesNamesJSON); }); WebApp.connectHandlers.use('/feedback', (req, res) => { diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx index 1fe1f16c0d4d10878f130e275aa76626184c3f78..737416f68982b6ad8fbb5c3fd8813c4dedab7e2d 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx @@ -1,7 +1,7 @@ import _ from 'lodash'; import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import { defineMessages, intlShape } from 'react-intl'; +import { defineMessages } from 'react-intl'; import Button from '/imports/ui/components/button/component'; import Dropdown from '/imports/ui/components/dropdown/component'; import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component'; @@ -17,7 +17,7 @@ import { styles } from '../styles'; const propTypes = { amIPresenter: PropTypes.bool.isRequired, - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, mountModal: PropTypes.func.isRequired, amIModerator: PropTypes.bool.isRequired, shortcuts: PropTypes.string, @@ -91,9 +91,9 @@ class ActionsDropdown extends PureComponent { this.makePresentationItems = this.makePresentationItems.bind(this); } - componentWillUpdate(nextProps) { - const { amIPresenter: isPresenter } = nextProps; - const { amIPresenter: wasPresenter, mountModal } = this.props; + componentDidUpdate(prevProps) { + const { amIPresenter: wasPresenter } = prevProps; + const { amIPresenter: isPresenter, mountModal } = this.props; if (wasPresenter && !isPresenter) { mountModal(null); } diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/captions/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/captions/component.jsx index 2ff8328728a4573ae8f4a4c9a5068696ff071798..3439d84102647a80f2d9e2d663ff0f53231b8372 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/captions/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/captions/component.jsx @@ -1,12 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import { styles } from '/imports/ui/components/actions-bar/styles'; import Button from '/imports/ui/components/button/component'; const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, isActive: PropTypes.bool.isRequired, handleOnClick: PropTypes.func.isRequired, }; diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/captions/reader-menu/styles.scss b/bigbluebutton-html5/imports/ui/components/actions-bar/captions/reader-menu/styles.scss index cea4ab59983f9ca25b0cf42cb93e4b279cdc0471..64aeda3cfc00ad9aabae190ba21e92745931b559 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/captions/reader-menu/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/captions/reader-menu/styles.scss @@ -1,4 +1,4 @@ -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/breakpoints"; @import "/imports/ui/components/modal/simple/styles"; @import "/imports/ui/stylesheets/mixins/focus"; diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx index a16e46f0c786f10f8d8bdbc15f59d0b18e16dad0..bae5e91009607f303f4f804be0cc59c2dd7d9c82 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx @@ -7,8 +7,38 @@ import AudioControlsContainer from '../audio/audio-controls/container'; import JoinVideoOptionsContainer from '../video-provider/video-button/container'; import CaptionsButtonContainer from '/imports/ui/components/actions-bar/captions/container'; import PresentationOptionsContainer from './presentation-options/component'; +import Button from '/imports/ui/components/button/component'; +import Storage from '/imports/ui/services/storage/session'; +import { ACTIONSBAR_HEIGHT } from '/imports/ui/components/layout/layout-manager'; +import { withLayoutConsumer } from '/imports/ui/components/layout/context'; class ActionsBar extends PureComponent { + constructor(props) { + super(props); + + this.autoArrangeToggle = this.autoArrangeToggle.bind(this); + } + + componentDidUpdate(prevProps) { + const { layoutContextState } = this.props; + const { layoutContextState: prevLayoutContextState } = prevProps; + const { autoArrangeLayout } = layoutContextState; + const { autoArrangeLayout: prevAutoArrangeLayout } = prevLayoutContextState; + if (autoArrangeLayout !== prevAutoArrangeLayout) this.forceUpdate(); + } + + autoArrangeToggle() { + const { layoutContextDispatch } = this.props; + const autoArrangeLayout = Storage.getItem('autoArrangeLayout'); + layoutContextDispatch( + { + type: 'setAutoArrangeLayout', + value: !autoArrangeLayout, + }, + ); + window.dispatchEvent(new Event('autoArrangeChanged')); + } + render() { const { amIPresenter, @@ -36,13 +66,19 @@ class ActionsBar extends PureComponent { } = this.props; const actionBarClasses = {}; + const autoArrangeLayout = Storage.getItem('autoArrangeLayout'); actionBarClasses[styles.centerWithActions] = amIPresenter; actionBarClasses[styles.center] = true; actionBarClasses[styles.mobileLayoutSwapped] = isLayoutSwapped && amIPresenter; return ( - <div className={styles.actionsbar}> + <div + className={styles.actionsbar} + style={{ + height: ACTIONSBAR_HEIGHT, + }} + > <div className={styles.left}> <ActionsDropdown {...{ amIPresenter, @@ -81,6 +117,18 @@ class ActionsBar extends PureComponent { screenshareDataSavingSetting, }} /> + <Button + className={cx(styles.button, autoArrangeLayout || styles.btn)} + icon={autoArrangeLayout ? 'lock' : 'unlock'} + color={autoArrangeLayout ? 'primary' : 'default'} + ghost={!autoArrangeLayout} + onClick={this.autoArrangeToggle} + label={autoArrangeLayout ? 'Disable Auto Arrange' : 'Enable Auto Arrange'} + aria-label="Auto Arrange test" + hideLabel + circle + size="lg" + /> </div> <div className={styles.right}> {isLayoutSwapped @@ -98,4 +146,4 @@ class ActionsBar extends PureComponent { } } -export default ActionsBar; +export default withLayoutConsumer(ActionsBar); diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx index 048b2e455996c0a85dc615496baa1dfce53435cb..6ff8858d482e68aedad306bf7ae8e834a097309c 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import _ from 'lodash'; import cx from 'classnames'; import browser from 'browser-detect'; @@ -109,11 +109,12 @@ const intlMessages = defineMessages({ }, }); +const BREAKOUT_LIM = Meteor.settings.public.app.breakoutRoomLimit; const MIN_BREAKOUT_ROOMS = 2; -const MAX_BREAKOUT_ROOMS = 8; +const MAX_BREAKOUT_ROOMS = BREAKOUT_LIM > MIN_BREAKOUT_ROOMS ? BREAKOUT_LIM : MIN_BREAKOUT_ROOMS; const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, isInvitation: PropTypes.bool.isRequired, meetingName: PropTypes.string.isRequired, users: PropTypes.arrayOf(PropTypes.object).isRequired, diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/styles.scss b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/styles.scss index 50b1d986040ae25747ddb6909ab9deecf0840969..65d2f3cdc3346cff79c7d4115c5abfc9cc9fd793 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/styles.scss @@ -1,4 +1,5 @@ -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/breakpoints"; +@import "/imports/ui/stylesheets/variables/placeholders"; @import "/imports/ui/stylesheets/mixins/_scrollable"; input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-inner-spin-button { @@ -36,8 +37,7 @@ input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-i } .content { - display: flex; - flex-direction: column; + @extend %flex-column; } .labelText { @@ -117,11 +117,12 @@ input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-i } .boxContainer { - height: 50vh; display: grid; - grid-template-columns: 1fr 1fr 1fr; - grid-template-rows: 33% 33% 33%; + grid-template-columns: repeat(3, minmax(4rem, 16rem)); + grid-template-rows: repeat(auto-fill, minmax(4rem, 8rem)); grid-gap: 1.5rem 1rem; + box-sizing: border-box; + padding-bottom: 1rem; } .changeToWarn { @@ -160,7 +161,7 @@ input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-i } .roomUserItem { - margin: 0; + @extend %no-margin; padding: .25rem 0 .25rem .25rem; overflow: hidden; text-overflow: ellipsis; @@ -181,14 +182,13 @@ input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-i /* mobile */ .listContainer { - display: flex; + @extend %flex-column; justify-content: flex-start; - flex-direction: column; } .itemTitle { + @extend %no-margin; color: var(--color-blue-light); - margin: 0; } .roomItem { @@ -332,8 +332,7 @@ input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-i } .checkBoxesContainer { - display: flex; - flex-direction: row; + @extend %flex-row; margin-top: 1rem; } diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/desktop-share/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/desktop-share/component.jsx index c2a148fc3bb559685f0d95dd27c4ce720ac04d8f..498f0d50e31dfd6260df0404aac21881db6bc3dd 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/desktop-share/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/desktop-share/component.jsx @@ -1,6 +1,6 @@ import React, { memo } from 'react'; import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import browser from 'browser-detect'; import Button from '/imports/ui/components/button/component'; import logger from '/imports/startup/client/logger'; @@ -12,7 +12,7 @@ import { styles } from '../styles'; import ScreenshareBridgeService from '/imports/api/screenshare/client/bridge/service'; const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, amIPresenter: PropTypes.bool.isRequired, handleShareScreen: PropTypes.func.isRequired, handleUnshareScreen: PropTypes.func.isRequired, diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/presentation-options/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/presentation-options/component.jsx index 909115eb6c91e15429b25d05605737a25cb78e8a..771d352ac2bac6b18b5b6a31d180a842e104baed 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/presentation-options/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/presentation-options/component.jsx @@ -1,11 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import Button from '/imports/ui/components/button/component'; import MediaService from '/imports/ui/components/media/service'; const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, toggleSwapLayout: PropTypes.func.isRequired, }; diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/quick-poll-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/quick-poll-dropdown/component.jsx index 5907f095095296cf689879353ec9416eb788ca38..448067de22bdba5c77a9870fd57377418f833fd7 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/quick-poll-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/quick-poll-dropdown/component.jsx @@ -1,6 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { defineMessages, intlShape } from 'react-intl'; +import { defineMessages } from 'react-intl'; import _ from 'lodash'; import { makeCall } from '/imports/ui/services/api'; import Button from '/imports/ui/components/button/component'; @@ -35,7 +35,7 @@ const intlMessages = defineMessages({ }); const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, parseCurrentSlideContent: PropTypes.func.isRequired, amIPresenter: PropTypes.bool.isRequired, }; diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/styles.scss b/bigbluebutton-html5/imports/ui/components/actions-bar/styles.scss index 6bc622f424c09f4ca033db6f3eb547e11a6ada6f..29ecbe58b3757e54ae1ae121712fb0c702d537c6 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/styles.scss @@ -1,11 +1,6 @@ -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/breakpoints"; @import "/imports/ui/components/modal/simple/styles"; -:root { - --mobile-swap-offset: 3.5rem; - --min-modal-height: 20rem; -} - .modal { @extend .modal; padding: var(--jumbo-padding-y); diff --git a/bigbluebutton-html5/imports/ui/components/activity-check/component.jsx b/bigbluebutton-html5/imports/ui/components/activity-check/component.jsx index 628a2e717a6b35050ce5bbddefac51d1ebd94748..61c1c74804beb7a3d4b63c2b16d56ac94a342630 100644 --- a/bigbluebutton-html5/imports/ui/components/activity-check/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/activity-check/component.jsx @@ -1,6 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { defineMessages, intlShape } from 'react-intl'; +import { defineMessages } from 'react-intl'; import Button from '/imports/ui/components/button/component'; import Modal from '/imports/ui/components/modal/simple/component'; @@ -9,7 +9,7 @@ import { makeCall } from '/imports/ui/services/api'; import { styles } from './styles'; const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, responseDelay: PropTypes.number.isRequired, }; @@ -64,6 +64,7 @@ class ActivityCheck extends Component { return setInterval(() => { const remainingTime = responseDelay - 1; + this.setState({ responseDelay: remainingTime, }); @@ -96,6 +97,7 @@ class ActivityCheck extends Component { <p>{intl.formatMessage(intlMessages.activityCheckLabel, { 0: responseDelay })}</p> <Button color="primary" + disabled={responseDelay <= 0} label={intl.formatMessage(intlMessages.activityCheckButton)} onClick={handleInactivityDismiss} role="button" diff --git a/bigbluebutton-html5/imports/ui/components/activity-check/styles.scss b/bigbluebutton-html5/imports/ui/components/activity-check/styles.scss index ce7c3b205f6ccf769fd91d9155a57cab08c348e9..da80838ecabbae1cf46ac9f8d48541cc6b6d9a3e 100644 --- a/bigbluebutton-html5/imports/ui/components/activity-check/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/activity-check/styles.scss @@ -1,5 +1,3 @@ -@import "/imports/ui/stylesheets/variables/_all"; - .activityModalContent { flex-direction: column; flex-grow: 1; diff --git a/bigbluebutton-html5/imports/ui/components/app/component.jsx b/bigbluebutton-html5/imports/ui/components/app/component.jsx index e138cc1d2ca0589897a4d3da108760926727b589..de234342eb1df53a028d46c750ae4e4a0beea859 100755 --- a/bigbluebutton-html5/imports/ui/components/app/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/app/component.jsx @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { throttle } from 'lodash'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import Modal from 'react-modal'; import browser from 'browser-detect'; import PanelManager from '/imports/ui/components/panel-manager/component'; @@ -25,6 +25,7 @@ import ManyWebcamsNotifier from '/imports/ui/components/video-provider/many-user import UploaderContainer from '/imports/ui/components/presentation/presentation-uploader/container'; import { withDraggableContext } from '../media/webcam-draggable-overlay/context'; import { styles } from './styles'; +import { NAVBAR_HEIGHT } from '/imports/ui/components/layout/layout-manager'; const MOBILE_MEDIA = 'only screen and (max-width: 40em)'; const APP_CONFIG = Meteor.settings.public.app; @@ -82,7 +83,7 @@ const propTypes = { actionsbar: PropTypes.element, captions: PropTypes.element, locale: PropTypes.string, - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, }; const defaultProps = { @@ -231,7 +232,12 @@ class App extends Component { if (!navbar) return null; return ( - <header className={styles.navbar}> + <header + className={styles.navbar} + style={{ + height: NAVBAR_HEIGHT, + }} + > {navbar} </header> ); diff --git a/bigbluebutton-html5/imports/ui/components/app/styles.scss b/bigbluebutton-html5/imports/ui/components/app/styles.scss index 0d03f0eba183c776db7a25b3910ff4078da5d792..7f2921ef1327d4535ba883a135d3482871d882fb 100755 --- a/bigbluebutton-html5/imports/ui/components/app/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/app/styles.scss @@ -1,7 +1,4 @@ @import "/imports/ui/stylesheets/variables/_all"; -@import "/imports/ui/stylesheets/variables/general"; -@import "/imports/ui/stylesheets/variables/palette"; -@import "/imports/ui/stylesheets/variables/typography"; @import '/imports/ui/stylesheets/mixins/_indicators'; :root { @@ -14,37 +11,34 @@ } .main { + @extend %flex-column; position: relative; height: 100%; - display: flex; - flex-direction: column; } .navbar { position: relative; text-align: center; - font-size: 1.5rem; padding: var(--bars-padding) var(--bars-padding) 0 var(--bars-padding); height: 7rem; } .wrapper { + @extend %flex-column; position: relative; - display: flex; flex: 1; - flex-direction: column; overflow: hidden; @include mq($medium-up) { - flex-direction: row; + flex-flow: row; } } %full-page { position: absolute; display: flex; - flex-direction: column; + flex-flow: column; overflow-y: auto; overflow-x: hidden; @@ -106,7 +100,7 @@ .userList { @extend %full-page; @extend %text-elipsis; - @include highContrastOutline(); + @extend %highContrastOutline; outline-style: solid; z-index: 3; overflow: visible; @@ -143,7 +137,7 @@ .captions, .chat { @extend %full-page; - @include highContrastOutline(); + @extend %highContrastOutline; outline-style: solid; order: 2; height: 100%; @@ -155,12 +149,12 @@ } @include mq($medium-up) { - flex: 0 25vw; + // flex: 0 25vw; order: 1; } @include mq($xlarge-up) { - flex-basis: 20vw; + // flex-basis: 20vw; } } @@ -172,10 +166,10 @@ .breakoutRoom { height: 100%; - width: 20vw; + // width: 20vw; background-color: var(--color-white); @include mq($small-only) { - width: auto; + // width: auto; height: auto; } } diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx index 69a613c1fddf4f72ea84d058eef5236c71ef2ec7..9846ae7e441e3b3d6bd66b23570b8e22aa3a673e 100755 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; -import { defineMessages, intlShape, injectIntl } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import Button from '/imports/ui/components/button/component'; import getFromUserSettings from '/imports/ui/services/users-settings'; import withShortcutHelper from '/imports/ui/components/shortcut-help/service'; @@ -37,7 +37,7 @@ const propTypes = { showMute: PropTypes.bool.isRequired, inAudio: PropTypes.bool.isRequired, listenOnly: PropTypes.bool.isRequired, - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, talking: PropTypes.bool.isRequired, }; diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/styles.scss b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/styles.scss index 49b567556555d5461bf17d3d95c93df3ffbe82db..d7c9bb73626d0873f87db1cb1ae73fddb0963974 100755 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/styles.scss @@ -1,4 +1,4 @@ -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/breakpoints"; @import "/imports/ui/components/actions-bar/styles.scss"; .container { diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-dial/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-dial/component.jsx index 7bf153b958d03d5b127224e900322ff3565cd61e..7ced6207b06d2e8b9bb9f0555b3df0b458100dd0 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-dial/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-dial/component.jsx @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { injectIntl, defineMessages, intlShape } from 'react-intl'; +import { injectIntl, defineMessages } from 'react-intl'; import { styles } from './styles'; const intlMessages = defineMessages({ @@ -23,7 +23,7 @@ const intlMessages = defineMessages({ }); const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, formattedDialNum: PropTypes.string.isRequired, telVoice: PropTypes.string.isRequired, }; diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-dial/styles.scss b/bigbluebutton-html5/imports/ui/components/audio/audio-dial/styles.scss index 5a40cfc3641c927b614b557248b0ca06bc7fde64..e1f6eaeb0ef6dd6adf3049ab003bbf3bed1709f6 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-dial/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-dial/styles.scss @@ -1,5 +1,3 @@ -@import "/imports/ui/stylesheets/variables/_all"; - :root { --audioDial-font-size: 2rem; } diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx index 6517ae540a9ee205c30fcaddde457496c71a988a..e79d9745349b2a77ad38894e8219b2afbd8bea18 100755 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx @@ -5,7 +5,7 @@ import Modal from '/imports/ui/components/modal/simple/component'; import Button from '/imports/ui/components/button/component'; import { Session } from 'meteor/session'; import { - defineMessages, injectIntl, intlShape, FormattedMessage, + defineMessages, injectIntl, FormattedMessage, } from 'react-intl'; import { styles } from './styles'; import PermissionsOverlay from '../permissions-overlay/component'; @@ -16,7 +16,7 @@ import AudioDial from '../audio-dial/component'; import AudioAutoplayPrompt from '../autoplay/component'; const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, closeModal: PropTypes.func.isRequired, joinMicrophone: PropTypes.func.isRequired, joinListenOnly: PropTypes.func.isRequired, diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/styles.scss b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/styles.scss index c42b8f4c8a8c21f8a2261bd3a4d5a0cbd0211209..572a0d3679fec6f0a36598fcf466fd413bb672bc 100755 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/styles.scss @@ -1,4 +1,4 @@ -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/breakpoints"; @import "/imports/ui/components/modal/simple/styles"; :root { diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-settings/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-settings/component.jsx index 82eb4668258423b57aeadaf641091aa034716853..4997b3b4b3a86a8afd3e01315a13ce254ae71178 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-settings/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-settings/component.jsx @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import Button from '/imports/ui/components/button/component'; import { withModalMounter } from '/imports/ui/components/modal/service'; import DeviceSelector from '/imports/ui/components/audio/device-selector/component'; @@ -9,7 +9,7 @@ import cx from 'classnames'; import { styles } from './styles'; const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, changeInputDevice: PropTypes.func.isRequired, changeOutputDevice: PropTypes.func.isRequired, handleBack: PropTypes.func.isRequired, diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-settings/styles.scss b/bigbluebutton-html5/imports/ui/components/audio/audio-settings/styles.scss index 5b28ea63cb417ea7cce594ac739b2854cf171edb..67f561ffe37ea85535969b2c0edbae68e6af221e 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-settings/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-settings/styles.scss @@ -1,5 +1,6 @@ -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/breakpoints"; @import '/imports/ui/stylesheets/mixins/_indicators'; +@import "/imports/ui/stylesheets/variables/placeholders"; .col, .formWrapper{ @@ -119,11 +120,11 @@ padding: .4rem; &:hover { - @include highContrastOutline(); + @extend %highContrastOutline; } &:focus { - @include highContrastOutline(); + @extend %highContrastOutline; outline-style: solid; } } diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-test/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-test/component.jsx index cd89de97c77e44d43181147eabd23815b2f3e431..6a571974cb1cd39839496116bb596c633e92baf4 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-test/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-test/component.jsx @@ -1,11 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import Button from '/imports/ui/components/button/component'; -import { defineMessages, intlShape, injectIntl } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import { styles } from './styles.scss'; const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, handlePlayAudioSample: PropTypes.func.isRequired, outputDeviceId: PropTypes.string, }; diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-test/styles.scss b/bigbluebutton-html5/imports/ui/components/audio/audio-test/styles.scss index f608e55b4ed6b9b5323ac59d5f70a89c603f0a05..7deb2c7e9561cdd3232798e632434a8889bd4772 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-test/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-test/styles.scss @@ -1,5 +1,3 @@ -@import "/imports/ui/stylesheets/variables/_all"; - .testAudioBtn { --hover-color: #0c5cb2; margin: 0 !important; diff --git a/bigbluebutton-html5/imports/ui/components/audio/autoplay/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/autoplay/component.jsx index d21cebe1def2ac02402aac98a205b41ab56ac315..18ee9e11b3353373e0ed107f324785d3282bc06f 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/autoplay/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/autoplay/component.jsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import Button from '/imports/ui/components/button/component'; -import { defineMessages, intlShape, injectIntl } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import { styles } from './styles'; @@ -18,7 +18,7 @@ const intlMessages = defineMessages({ const propTypes = { handleAllowAutoplay: PropTypes.func.isRequired, - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, }; class AudioAutoplayPrompt extends PureComponent { diff --git a/bigbluebutton-html5/imports/ui/components/audio/autoplay/styles.scss b/bigbluebutton-html5/imports/ui/components/audio/autoplay/styles.scss index 9bc3faa24bbf7bd44c385becb9c96712dcc8fab6..e9ec0a7ac28cc080e62a0540f90564b58b202da4 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/autoplay/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/audio/autoplay/styles.scss @@ -1,5 +1,3 @@ -@import "/imports/ui/stylesheets/variables/_all"; - .autoplayPrompt { margin-top: auto; margin-bottom: auto; diff --git a/bigbluebutton-html5/imports/ui/components/audio/echo-test/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/echo-test/component.jsx index 8c6aef33c212c29026f531e1c1c8b4bdc2ab85ef..1f1553097d7d0e99525300f0a58c9e3e5dd86908 100755 --- a/bigbluebutton-html5/imports/ui/components/audio/echo-test/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/echo-test/component.jsx @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { Session } from 'meteor/session'; import Button from '/imports/ui/components/button/component'; -import { defineMessages, intlShape, injectIntl } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import { styles } from './styles'; const intlMessages = defineMessages({ @@ -27,7 +27,7 @@ const intlMessages = defineMessages({ const propTypes = { handleYes: PropTypes.func.isRequired, handleNo: PropTypes.func.isRequired, - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, }; class EchoTest extends Component { diff --git a/bigbluebutton-html5/imports/ui/components/audio/echo-test/styles.scss b/bigbluebutton-html5/imports/ui/components/audio/echo-test/styles.scss index dd266f725f456ffd0d21a039ad534112de36f624..6eb6eb9c585b1ad181676a890bcafc82fe6e2d51 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/echo-test/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/audio/echo-test/styles.scss @@ -1,4 +1,4 @@ -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/breakpoints"; .echoTest { margin-top: auto; diff --git a/bigbluebutton-html5/imports/ui/components/audio/help/styles.scss b/bigbluebutton-html5/imports/ui/components/audio/help/styles.scss index 9a7489785e04fa23aa27edb083845bff4f878c1d..011eee5d701e79dbe289565053653048089b4a68 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/help/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/audio/help/styles.scss @@ -1,4 +1,4 @@ -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/breakpoints"; :root { --help-err-height: 10rem; diff --git a/bigbluebutton-html5/imports/ui/components/audio/permissions-overlay/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/permissions-overlay/component.jsx index a1ac3e0e65352070cb45151af9eaaff029dd08de..0cd06c4ec933c0cf949ef14dc8459a90dfe1612b 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/permissions-overlay/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/permissions-overlay/component.jsx @@ -1,11 +1,11 @@ import React from 'react'; -import { injectIntl, intlShape, defineMessages } from 'react-intl'; +import { injectIntl, defineMessages } from 'react-intl'; import Modal from '/imports/ui/components/modal/simple/component'; import PropTypes from 'prop-types'; import { styles } from './styles'; const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, closeModal: PropTypes.func.isRequired, }; diff --git a/bigbluebutton-html5/imports/ui/components/audio/permissions-overlay/styles.scss b/bigbluebutton-html5/imports/ui/components/audio/permissions-overlay/styles.scss index 74c39e53ebdd4ff5cf4e00a81a394e63a90711e6..0267c58b46da4d08b5b743395779df9841aceb10 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/permissions-overlay/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/audio/permissions-overlay/styles.scss @@ -1,5 +1,3 @@ -@import "../../../stylesheets/variables/_all"; - :root { --position-left-edge: 60%; --position-bottom-edge: 5%; diff --git a/bigbluebutton-html5/imports/ui/components/banner-bar/styles.scss b/bigbluebutton-html5/imports/ui/components/banner-bar/styles.scss index 3421702e34bdd94b808e774631f708263a370ad8..b8d073ed64e760f3b4f8a318684fe92cfc517501 100644 --- a/bigbluebutton-html5/imports/ui/components/banner-bar/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/banner-bar/styles.scss @@ -1,4 +1,4 @@ -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/placeholders"; .bannerTextColor { @extend %text-elipsis; diff --git a/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx b/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx index f05a09ea04f9be87c8f3ecab5e0fef738bfc71aa..dd1ed47b9e71892799c11517663c3822b9a3f6d9 100755 --- a/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx @@ -1,5 +1,5 @@ import React, { Component } from 'react'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import { withModalMounter } from '/imports/ui/components/modal/service'; import Modal from '/imports/ui/components/modal/fullscreen/component'; import logger from '/imports/startup/client/logger'; @@ -15,11 +15,11 @@ const intlMessages = defineMessages({ }, message: { id: 'app.breakoutJoinConfirmation.message', - description: 'Join breakout confim message', + description: 'Join breakout confirm message', }, freeJoinMessage: { id: 'app.breakoutJoinConfirmation.freeJoinMessage', - description: 'Join breakout confim message', + description: 'Join breakout confirm message', }, confirmLabel: { id: 'app.createBreakoutRoom.join', @@ -40,7 +40,7 @@ const intlMessages = defineMessages({ }); const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, breakout: PropTypes.objectOf(Object).isRequired, getURL: PropTypes.func.isRequired, mountModal: PropTypes.func.isRequired, @@ -88,10 +88,15 @@ class BreakoutJoinConfirmation extends Component { breakoutURL, isFreeJoin, voiceUserJoined, + requestJoinURL, } = this.props; const { selectValue } = this.state; - const url = isFreeJoin ? getURL(selectValue) : breakoutURL; + if (!getURL(selectValue)) { + requestJoinURL(selectValue); + } + const urlFromSelectedRoom = getURL(selectValue); + const url = isFreeJoin ? urlFromSelectedRoom : breakoutURL; if (voiceUserJoined) { // leave main room's audio when joining a breakout room @@ -103,6 +108,12 @@ class BreakoutJoinConfirmation extends Component { } VideoService.exitVideo(); + if (url === '') { + logger.error({ + logCode: 'breakoutjoinconfirmation_redirecting_to_url', + extraInfo: { breakoutURL, isFreeJoin }, + }, 'joining breakout room but redirected to about://blank'); + } window.open(url); mountModal(null); } diff --git a/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/styles.scss b/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/styles.scss index 162bcb5b415c9e9ff8279dcb0e4e59488b8e0544..fc38af18114c7850cdb5301cdc4556256c09f416 100755 --- a/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/styles.scss @@ -1,5 +1,3 @@ -@import "../../stylesheets/variables/_all"; - .selectParent { display: flex; flex-direction: column; diff --git a/bigbluebutton-html5/imports/ui/components/breakout-room/component.jsx b/bigbluebutton-html5/imports/ui/components/breakout-room/component.jsx index 7ecdbff97de981830629d342e1c4a9ba3b4c5c69..27c7de6fac00e5d7faec23da35b9f3d4e77fc99b 100644 --- a/bigbluebutton-html5/imports/ui/components/breakout-room/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/breakout-room/component.jsx @@ -261,7 +261,7 @@ class BreakoutRoom extends PureComponent { > <div className={styles.content} key={`breakoutRoomList-${breakout.breakoutId}`}> <span aria-hidden> - {intl.formatMessage(intlMessages.breakoutRoom, breakout.sequence.toString())} + {intl.formatMessage(intlMessages.breakoutRoom, { 0: breakout.sequence })} <span className={styles.usersAssignedNumberLabel}> ( {breakout.joinedUsers.length} diff --git a/bigbluebutton-html5/imports/ui/components/breakout-room/service.js b/bigbluebutton-html5/imports/ui/components/breakout-room/service.js index 28d549d134b134eb3dd261ec8bf1c0b26746dfa1..9fe60517c9b3cb73a8d9d9d132aa8e11b1b3ecfb 100644 --- a/bigbluebutton-html5/imports/ui/components/breakout-room/service.js +++ b/bigbluebutton-html5/imports/ui/components/breakout-room/service.js @@ -27,7 +27,10 @@ const breakoutRoomUser = (breakoutId) => { return breakoutUser; }; -const closeBreakoutPanel = () => Session.set('openPanel', 'userlist'); +const closeBreakoutPanel = () => { + Session.set('openPanel', 'userlist'); + window.dispatchEvent(new Event('panelChanged')); +}; const endAllBreakouts = () => { makeCall('endAllBreakouts'); diff --git a/bigbluebutton-html5/imports/ui/components/breakout-room/styles.scss b/bigbluebutton-html5/imports/ui/components/breakout-room/styles.scss index fb84c04448928326a07f7d5cb209087dbe2ec184..458871c27e1fa7b94956a2717fe2813ee7a69bb2 100644 --- a/bigbluebutton-html5/imports/ui/components/breakout-room/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/breakout-room/styles.scss @@ -1,7 +1,7 @@ -@import "/imports/ui/stylesheets/variables/_all"; @import "/imports/ui/stylesheets/mixins/_scrollable"; -@import "/imports/ui/components/user-list/styles"; -@import "/imports/ui/components/user-list/user-list-content/styles"; +@import "/imports/ui/stylesheets/mixins/focus"; +@import "/imports/ui/stylesheets/mixins/_indicators"; +@import "/imports/ui/stylesheets/variables/placeholders"; .panel { @include scrollbox-vertical(); @@ -162,10 +162,29 @@ } .breakoutColumn { - @extend .userListColumn; + display: flex; + flex-flow: column; + min-height: 0; + flex-grow: 1; } .breakoutScrollableList { - @extend .scrollableList; + @include elementFocus(var(--list-item-bg-hover)); + @include scrollbox-vertical(var(--user-list-bg)); + @extend %highContrastOutline; + + &:focus-within, + &:focus { + outline-style: solid; + } + + &:active { + box-shadow: none; + border-radius: none; + } + + overflow-x: hidden; + outline-width: 1px !important; + outline-color: transparent !important; background: none; } \ No newline at end of file diff --git a/bigbluebutton-html5/imports/ui/components/button/styles.scss b/bigbluebutton-html5/imports/ui/components/button/styles.scss index 31063b282ceb34eec443b9e2009878e2b48ff1fd..a3fef2b8b697e271d73e920304add0edd85176ee 100755 --- a/bigbluebutton-html5/imports/ui/components/button/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/button/styles.scss @@ -1,5 +1,5 @@ -@import "/imports/ui/stylesheets/variables/_all"; @import "/imports/ui/stylesheets/mixins/_indicators"; +@import "/imports/ui/stylesheets/variables/placeholders"; /* Base * ========== @@ -63,13 +63,13 @@ &:hover, &:focus { - @include highContrastOutline(); + @extend %highContrastOutline; text-decoration: none; } &:active, &:focus { - @include highContrastOutline(); + @extend %highContrastOutline; outline-style: solid; } @@ -105,7 +105,7 @@ &:focus, &:hover { - @include highContrastOutline(); + @extend %highContrastOutline; } &:focus { @@ -116,7 +116,7 @@ &:focus { span:first-of-type::before { border-radius: 50%; - @include highContrastOutline(); + @extend %highContrastOutline; outline-style: solid; } } diff --git a/bigbluebutton-html5/imports/ui/components/captions/pad/styles.scss b/bigbluebutton-html5/imports/ui/components/captions/pad/styles.scss index 9bc5b5b84ffed9cf6fa7597f84bbc2d19efd3065..a4f25982cf64ba0e9c3efa6dc06fe6a9aa0dd353 100644 --- a/bigbluebutton-html5/imports/ui/components/captions/pad/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/captions/pad/styles.scss @@ -1,5 +1,6 @@ @import "/imports/ui/stylesheets/mixins/focus"; -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/breakpoints"; +@import "/imports/ui/stylesheets/variables/placeholders"; :root { --speech-results-width: 22.3rem; @@ -19,34 +20,35 @@ .pad { background-color: var(--color-white); - padding: var(--md-padding-x); + padding: + var(--md-padding-x) + var(--md-padding-y) + var(--md-padding-x) + var(--md-padding-x); + display: flex; flex-grow: 1; flex-direction: column; justify-content: space-around; overflow: hidden; height: 100vh; - transform: translateZ(0); + + :global(.browser-chrome) & { + transform: translateZ(0); + } + + @include mq($small-only) { + transform: none !important; + } } .header { + position: relative; + top: var(--poll-header-offset); display: flex; flex-direction: row; - align-items: left; - flex-shrink: 0; - - a { - @include elementFocus(var(--color-primary)); - padding-bottom: var(--sm-padding-y); - padding-left: var(--sm-padding-y); - text-decoration: none; - display: block; - } - - [class^="icon-bbb-"], - [class*=" icon-bbb-"] { - font-size: 85%; - } + align-items: center; + justify-content: space-between; } .title { @@ -54,9 +56,7 @@ flex: 1; & > button, button:hover { - margin-top: 0; - padding-top: 0; - border-top: 0; + max-width: var(--toast-content-width); } } @@ -64,12 +64,27 @@ position: relative; background-color: var(--color-white); display: block; - margin: 4px; - margin-bottom: 2px; - padding-left: 0px; + margin: var(--border-size-large); + margin-bottom: var(--border-size); + padding-left: 0; + padding-right: inherit; + + [dir="rtl"] & { + padding-left: inherit; + padding-right: 0; + } > i { - color: black; + color: var(--color-gray-dark); + font-size: smaller; + + [dir="rtl"] & { + -webkit-transform: scale(-1, 1); + -moz-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + -o-transform: scale(-1, 1); + transform: scale(-1, 1); + } } &:hover { diff --git a/bigbluebutton-html5/imports/ui/components/captions/writer-menu/styles.scss b/bigbluebutton-html5/imports/ui/components/captions/writer-menu/styles.scss index a5f98eeb7e99ab5776aa3500cc467d5661c8b719..94115c346768a0861cab6524371e5218b0b65a65 100644 --- a/bigbluebutton-html5/imports/ui/components/captions/writer-menu/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/captions/writer-menu/styles.scss @@ -1,6 +1,6 @@ -@import "/imports/ui/stylesheets/variables/_all"; @import "/imports/ui/components/modal/simple/styles"; @import "/imports/ui/stylesheets/mixins/focus"; +@import "/imports/ui/stylesheets/variables/placeholders"; .header { margin: 0; @@ -46,11 +46,11 @@ padding: 1px; &:hover { - @include highContrastOutline(); + @extend %highContrastOutline; } &:focus { - @include highContrastOutline(); + @extend %highContrastOutline; outline-style: solid; } } diff --git a/bigbluebutton-html5/imports/ui/components/chat/alert/push-alert/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/alert/push-alert/component.jsx index 31a3a02ed888311402c7373cb6d01f6d11c960a4..db51df75ae5971c6d95a30f5664f06c16123dd21 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/alert/push-alert/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/alert/push-alert/component.jsx @@ -1,6 +1,5 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import _ from 'lodash'; import injectNotify from '/imports/ui/components/toast/inject-notify/component'; import { Session } from 'meteor/session'; @@ -31,6 +30,7 @@ class ChatPushAlert extends PureComponent { onClick={() => { Session.set('openPanel', 'chat'); Session.set('idChatOpen', chat); + window.dispatchEvent(new Event('panelChanged')); }} onKeyPress={() => null} > diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx index 73d65f226dac6720242a7032ad7696ef294fd4e5..dd275989d618916a9774e0c5c41a7a67943385f7 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx @@ -71,7 +71,9 @@ class ChatDropdown extends PureComponent { } getAvailableActions() { - const { intl, isMeteorConnected, amIModerator } = this.props; + const { + intl, isMeteorConnected, amIModerator, meetingIsBreakout, meetingName, + } = this.props; const clearIcon = 'delete'; const saveIcon = 'download'; @@ -86,8 +88,11 @@ class ChatDropdown extends PureComponent { onClick={() => { const link = document.createElement('a'); const mimeType = 'text/plain'; + const date = new Date(); + const time = `${date.getHours()}-${date.getMinutes()}`; + const dateString = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}_${time}`; - link.setAttribute('download', `public-chat-${Date.now()}.txt`); + link.setAttribute('download', `bbb-${meetingName}[public-chat]_${dateString}.txt`); link.setAttribute( 'href', `data: ${mimeType} ;charset=utf-8, @@ -103,7 +108,7 @@ class ChatDropdown extends PureComponent { label={intl.formatMessage(intlMessages.copy)} key={this.actionsKey[1]} />, - amIModerator && isMeteorConnected ? ( + !meetingIsBreakout && amIModerator && isMeteorConnected ? ( <DropdownListItem data-test="chatClear" icon={clearIcon} diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/container.jsx new file mode 100644 index 0000000000000000000000000000000000000000..f00a5d6cc173a9276a4a19c549c70381b6af874c --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/container.jsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { withTracker } from 'meteor/react-meteor-data'; +import Auth from '/imports/ui/services/auth'; +import Meetings from '/imports/api/meetings'; +import ChatDropdown from './component'; + +const ChatDropdownContainer = ({ ...props }) => <ChatDropdown {...props} />; + +export default withTracker(() => { + const getMeetingName = () => { + const m = Meetings.findOne({ meetingId: Auth.meetingID }, + { fields: { 'meetingProp.name': 1 } }); + return m.meetingProp.name; + }; + + return { + meetingName: getMeetingName(), + }; +})(ChatDropdownContainer); diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/styles.scss index 87d63edb9a850cebed08b6fa0a1e96d0a89b6ecf..1e4e51b0811750d6942f622fcd3648f2d4f9e82f 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/styles.scss @@ -1,5 +1,3 @@ -@import "/imports/ui/stylesheets/variables/_all"; - .btn { --icon-offset: -.4em; --square-side-length: 1.56rem; diff --git a/bigbluebutton-html5/imports/ui/components/chat/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/component.jsx index e19380a741ebb8d2065d2ac6f9fa7ae76652a8f7..534bc851e55021b717f01c1f690a80bbe7648f70 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/component.jsx @@ -8,7 +8,7 @@ import withShortcutHelper from '/imports/ui/components/shortcut-help/service'; import { styles } from './styles.scss'; import MessageForm from './message-form/container'; import MessageList from './message-list/container'; -import ChatDropdown from './chat-dropdown/component'; +import ChatDropdownContainer from './chat-dropdown/container'; const ELEMENT_ID = 'chat-messages'; @@ -41,6 +41,7 @@ const Chat = (props) => { minMessageLength, maxMessageLength, amIModerator, + meetingIsBreakout, } = props; const HIDE_CHAT_AK = shortcuts.hidePrivateChat; @@ -60,6 +61,7 @@ const Chat = (props) => { onClick={() => { Session.set('idChatOpen', ''); Session.set('openPanel', 'userlist'); + window.dispatchEvent(new Event('panelChanged')); }} aria-label={intl.formatMessage(intlMessages.hideChatLabel, { 0: title })} accessKey={HIDE_CHAT_AK} @@ -81,13 +83,14 @@ const Chat = (props) => { actions.handleClosePrivateChat(chatID); Session.set('idChatOpen', ''); Session.set('openPanel', 'userlist'); + window.dispatchEvent(new Event('panelChanged')); }} aria-label={intl.formatMessage(intlMessages.closeChatLabel, { 0: title })} label={intl.formatMessage(intlMessages.closeChatLabel, { 0: title })} accessKey={CLOSE_CHAT_AK} /> ) - : <ChatDropdown isMeteorConnected={isMeteorConnected} amIModerator={amIModerator} /> + : <ChatDropdownContainer {...{ meetingIsBreakout, isMeteorConnected, amIModerator }} /> } </header> <MessageList diff --git a/bigbluebutton-html5/imports/ui/components/chat/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/container.jsx index 5ba32ed22a049695388087aa476130ba3e2b20d5..c471538140409fbb9b92e04c094d674da6c47ab0 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/container.jsx @@ -3,9 +3,10 @@ import { defineMessages, injectIntl } from 'react-intl'; import { withTracker } from 'meteor/react-meteor-data'; import { Session } from 'meteor/session'; import Auth from '/imports/ui/services/auth'; +import Storage from '/imports/ui/services/storage/session'; +import { meetingIsBreakout } from '/imports/ui/components/app/service'; import Chat from './component'; import ChatService from './service'; -import Storage from '/imports/ui/services/storage/session'; const CHAT_CONFIG = Meteor.settings.public.chat; const PUBLIC_CHAT_KEY = CHAT_CONFIG.public_id; @@ -176,6 +177,7 @@ export default injectIntl(withTracker(({ intl }) => { isChatLocked, isMeteorConnected, amIModerator, + meetingIsBreakout: meetingIsBreakout(), actions: { handleClosePrivateChat: ChatService.closePrivateChat, }, diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx index d7507ee471a7e01a6984c7b0ba978b7fb75ed3de..b0db2da66446a705cd6e21f6d98bc3d732c6d9ff 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx @@ -1,5 +1,5 @@ import React, { PureComponent } from 'react'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import cx from 'classnames'; import TextareaAutosize from 'react-autosize-textarea'; import browser from 'browser-detect'; @@ -10,7 +10,7 @@ import { styles } from './styles.scss'; import Button from '../../button/component'; const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, chatId: PropTypes.string.isRequired, disabled: PropTypes.bool.isRequired, minMessageLength: PropTypes.number.isRequired, diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-form/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/message-form/styles.scss index b280712b65283b212f866b80030f85c0b595edc1..d9cc1f2ab72c88985e12cb2e9e82bafc0a485d5e 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/message-form/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/chat/message-form/styles.scss @@ -1,6 +1,6 @@ @import "/imports/ui/stylesheets/mixins/focus"; @import "/imports/ui/stylesheets/mixins/_indicators"; -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/placeholders"; :root { --max-chat-input-msg-height: .93rem; @@ -78,14 +78,10 @@ background-color: rgba(167,179,189,0.25); } - &:hover { - @include highContrastOutline(); - } - + &:hover, &:active, &:focus { - @include highContrastOutline(); - outline-style: solid; + @extend %highContrastOutline; } } diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/component.jsx index 3dcacf0063bc31600fa4d72f64804f3a8771c817..b379f75af8054046c04c615b2ebfc0391047c809 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/component.jsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import { - defineMessages, injectIntl, intlShape, FormattedMessage, + defineMessages, injectIntl, FormattedMessage, } from 'react-intl'; import browser from 'browser-detect'; import PropTypes from 'prop-types'; @@ -8,7 +8,7 @@ import cx from 'classnames'; import { styles } from '../styles.scss'; const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, typingUsers: PropTypes.arrayOf(Object).isRequired, }; diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx index 9ea5eab539d232d3ef1c5192db91415f72d82d65..fc92caaa82e71df4f853e321a5b240a4f8d909ad 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx @@ -147,7 +147,7 @@ class MessageList extends Component { handleScrollUpdate, } = this.props; - if (position !== null && position + target.offsetHeight === target.scrollHeight) { + if (position !== null && position + target?.offsetHeight === target?.scrollHeight) { // I used one because the null value is used to notify that // the user has sent a message and the message list should scroll to bottom handleScrollUpdate(1); @@ -159,10 +159,10 @@ class MessageList extends Component { handleScrollChange(e) { const { scrollArea } = this.state; - const scrollCursorPosition = e.scrollTop + scrollArea.offsetHeight; + const scrollCursorPosition = e.scrollTop + scrollArea?.offsetHeight; const shouldScrollBottom = e.scrollTop === null - || scrollCursorPosition === scrollArea.scrollHeight - || (scrollArea.scrollHeight - scrollCursorPosition < 1); + || scrollCursorPosition === scrollArea?.scrollHeight + || (scrollArea?.scrollHeight - scrollCursorPosition < 1); if ((e.scrollTop < this.lastKnowScrollPosition) && !shouldScrollBottom) { this.setState({ shouldScrollToBottom: false }); diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx index 4e9ebea3bfef182ca16827ef97796a5081ff4b3f..0c0df937f6daf218e14306fa668e29bebd82fcbf 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx @@ -4,6 +4,7 @@ import { FormattedTime, defineMessages, injectIntl } from 'react-intl'; import _ from 'lodash'; import Icon from '/imports/ui/components/icon/component'; import UserAvatar from '/imports/ui/components/user-avatar/component'; +import cx from 'classnames'; import Message from './message/component'; import { styles } from './styles'; @@ -42,10 +43,6 @@ const intlMessages = defineMessages({ id: 'app.chat.pollResult', description: 'used in place of user name who published poll to chat', }, - legendTitle: { - id: 'app.polling.pollingTitle', - description: 'heading for chat poll legend', - }, }); class MessageListItem extends Component { @@ -111,13 +108,14 @@ class MessageListItem extends Component { handleReadMessage, scrollArea, intl, - chats, + messages, } = this.props; - if (chats.length < 1) return null; + if (messages && messages[0].text.includes('bbb-published-poll-<br/>')) { + return this.renderPollItem(); + } const dateTime = new Date(time); - const regEx = /<a[^>]+>/i; return ( @@ -128,6 +126,7 @@ class MessageListItem extends Component { className={styles.avatar} color={user.color} moderator={user.isModerator} + avatar={user.avatar} > {user.name.toLowerCase().slice(0, 2)} </UserAvatar> @@ -149,7 +148,7 @@ class MessageListItem extends Component { </time> </div> <div className={styles.messages} data-test="chatUserMessage"> - {chats.map(message => ( + {messages.map(message => ( <Message className={(regEx.test(message.text) ? styles.hyperlink : styles.message)} key={message.id} @@ -173,53 +172,17 @@ class MessageListItem extends Component { user, time, intl, - polls, isDefaultPoll, + messages, + scrollArea, + chatAreaId, + lastReadMessageTime, + handleReadMessage, } = this.props; - if (polls.length < 1) return null; - const dateTime = new Date(time); - let pollText = []; - const pollElement = []; - const legendElements = [ - (<div - className={styles.optionsTitle} - key={_.uniqueId('chat-poll-options-')} - > - {intl.formatMessage(intlMessages.legendTitle)} - </div>), - ]; - - let isDefault = true; - polls.forEach((poll) => { - isDefault = isDefaultPoll(poll.text); - pollText = poll.text.split('<br/>'); - pollElement.push(pollText.map((p, index) => { - if (!isDefault) { - legendElements.push( - <div key={_.uniqueId('chat-poll-legend-')} className={styles.pollLegend}> - <span>{`${index + 1}: `}</span> - <span className={styles.pollOption}>{p.split(':')[0]}</span> - </div>, - ); - } - - return ( - <div key={_.uniqueId('chat-poll-result-')} className={styles.pollLine}> - {!isDefault ? p.replace(p.split(':')[0], index + 1) : p} - </div> - ); - })); - }); - - if (!isDefault) { - pollElement.push(<div key={_.uniqueId('chat-poll-separator-')} className={styles.divider} />); - pollElement.push(legendElements); - } - - return polls ? ( + return messages ? ( <div className={styles.item} key={_.uniqueId('message-poll-item-')}> <div className={styles.wrapper} ref={(ref) => { this.item = ref; }}> <div className={styles.avatarWrapper}> @@ -240,15 +203,19 @@ class MessageListItem extends Component { <FormattedTime value={dateTime} /> </time> </div> - <div className={styles.messages}> - {polls[0] ? ( - <div className={styles.pollWrapper} style={{ borderLeft: `3px ${user.color} solid` }}> - { - pollElement - } - </div> - ) : null} - </div> + <Message + type="poll" + className={cx(styles.message, styles.pollWrapper)} + key={messages[0].id} + text={messages[0].text} + time={messages[0].time} + chatAreaId={chatAreaId} + lastReadMessageTime={lastReadMessageTime} + handleReadMessage={handleReadMessage} + scrollArea={scrollArea} + color={user.color} + isDefaultPoll={isDefaultPoll(messages[0].text.replace('bbb-published-poll-<br/>', ''))} + /> </div> </div> </div> @@ -266,10 +233,7 @@ class MessageListItem extends Component { return ( <div className={styles.item}> - {[ - this.renderPollItem(), - this.renderMessageItem(), - ]} + {this.renderMessageItem()} </div> ); } diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/container.jsx index 5c7d7e0a476776ecaa42ebaea97dfd9c5f25a3f7..31b407d453f56cb6fe98a48f4cbd0c49b4f97bcb 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/container.jsx @@ -15,26 +15,10 @@ export default withTracker(({ message }) => { const mappedMessage = ChatService.mapGroupMessage(message); const messages = mappedMessage.content; - const chats = []; - const polls = []; - - if (messages.length > 0) { - messages.forEach((m) => { - if (m.text.includes('bbb-published-poll-<br/>')) { - m.text = m.text.replace(/^bbb-published-poll-<br\/>/g, ''); - polls.push(m); - } else { - chats.push(m); - } - }); - } - return { messages, user: mappedMessage.sender, time: mappedMessage.time, - chats, - polls, isDefaultPoll: (pollText) => { const pollValue = pollText.replace(/<br\/>|[ :|%\n\d+]/g, ''); switch (pollValue) { diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/message/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/message/component.jsx index e015975f84c037b8d95cc03d942a3f12865d1292..d7d7ade04ff122e76cacc0b88d9ab8b76ceb043c 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/message/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/message/component.jsx @@ -2,6 +2,7 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import _ from 'lodash'; import fastdom from 'fastdom'; +import { defineMessages, injectIntl } from 'react-intl'; const propTypes = { text: PropTypes.string.isRequired, @@ -34,13 +35,22 @@ const isElementInViewport = (el) => { ); }; -export default class MessageListItem extends PureComponent { +const intlMessages = defineMessages({ + legendTitle: { + id: 'app.polling.pollingTitle', + description: 'heading for chat poll legend', + }, +}); + +class MessageListItem extends PureComponent { constructor(props) { super(props); this.ticking = false; this.handleMessageInViewport = _.debounce(this.handleMessageInViewport.bind(this), 50); + + this.renderPollListItem = this.renderPollListItem.bind(this); } componentDidMount() { @@ -145,17 +155,56 @@ export default class MessageListItem extends PureComponent { }); } + renderPollListItem() { + const { + intl, + text, + className, + color, + isDefaultPoll, + } = this.props; + + const formatBoldBlack = s => s.bold().fontcolor('black'); + + let _text = text.replace('bbb-published-poll-<br/>', ''); + + if (!isDefaultPoll) { + const entries = _text.split('<br/>'); + const options = []; + entries.map((e) => { options.push([e.slice(0, e.indexOf(':'))]); return e; }); + options.map((o, idx) => { + _text = formatBoldBlack(_text.replace(o, idx + 1)); + return _text; + }); + _text += formatBoldBlack(`<br/><br/>${intl.formatMessage(intlMessages.legendTitle)}`); + options.map((o, idx) => { _text += `<br/>${idx + 1}: ${o}`; return _text; }); + } + + return ( + <p + className={className} + style={{ borderLeft: `3px ${color} solid` }} + ref={(ref) => { this.text = ref; }} + dangerouslySetInnerHTML={{ __html: isDefaultPoll ? formatBoldBlack(_text) : _text }} + data-test="chatMessageText" + /> + ); + } + render() { const { text, + type, className, } = this.props; + if (type === 'poll') return this.renderPollListItem(); + return ( <p + className={className} ref={(ref) => { this.text = ref; }} dangerouslySetInnerHTML={{ __html: text }} - className={className} data-test="chatMessageText" /> ); @@ -164,3 +213,5 @@ export default class MessageListItem extends PureComponent { MessageListItem.propTypes = propTypes; MessageListItem.defaultProps = defaultProps; + +export default injectIntl(MessageListItem); diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss index 5b8817b1c6822a1662591941eb2c25846fa98abb..d8e8d047804e0455ceb3ea81948563df58971297 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss @@ -1,10 +1,10 @@ -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/placeholders"; :root { --systemMessage-background-color: #F9FBFC; --systemMessage-border-color: #C5CDD4; --systemMessage-font-color: var(--color-dark-grey); - --chat-poll-margin-sm: .25rem; + --chat-poll-margin-sm: .5rem; } .item { @@ -159,42 +159,11 @@ bottom: var(--border-size-large); } -.pollLine { - overflow-wrap: break-word; - font-size: var(--font-size-large); - font-weight: 600; -} - .pollWrapper { + background: var(--systemMessage-background-color); border: solid 1px var(--color-gray-lighter); border-radius: var(--border-radius); padding: var(--chat-poll-margin-sm); padding-left: 1rem; - margin-top: .5rem; - background: var(--systemMessage-background-color); -} - -.pollLegend { - display: flex; - flex-direction: row; - margin-top: var(--chat-poll-margin-sm); -} - -.pollOption { - word-break: break-word; - margin-left: var(--md-padding-y); -} - -.optionsTitle { - font-weight: bold; - margin-top: var(--md-padding-y); -} - -.divider { - position: relative; - height: 1px; - width: 95%; - margin-right: auto; - margin-top: var(--md-padding-y); - border-bottom: solid 1px var(--color-gray-lightest); + margin-top: var(--chat-poll-margin-sm) !important; } diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss index 1e5477dc71ce537cf55eee595d8d0a45fab1c1dc..b97fdb5bea9162ef2087036068a5298ee2c2e4af 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss @@ -1,5 +1,5 @@ -@import "/imports/ui/stylesheets/variables/_all"; @import "/imports/ui/stylesheets/mixins/_scrollable"; +@import "/imports/ui/stylesheets/variables/placeholders"; .messageListWrapper { display: flex; @@ -33,6 +33,7 @@ right: 0 var(--md-padding-x) 0 0; padding-top: 0; width: 100%; + outline-style: none; [dir="rtl"] & { margin: 0 0 0 auto; diff --git a/bigbluebutton-html5/imports/ui/components/chat/service.js b/bigbluebutton-html5/imports/ui/components/chat/service.js index 5579ad7ce4ab1d7612b3dda360ea71d668efcf58..a2a8fec5d72503848f6ef297cfcbd403e04a8f7e 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/service.js +++ b/bigbluebutton-html5/imports/ui/components/chat/service.js @@ -31,6 +31,8 @@ const UnsentMessagesCollection = new Mongo.Collection(null); // session for closed chat list const CLOSED_CHAT_LIST_KEY = 'closedChatList'; +const POLL_MESSAGE_PREFIX = 'bbb-published-poll-<br/>'; + const getUser = userId => Users.findOne({ userId }); const getWelcomeProp = () => Meetings.findOne({ meetingId: Auth.meetingID }, @@ -48,13 +50,18 @@ const mapGroupMessage = (message) => { const sender = Users.findOne({ userId: message.sender }, { fields: { - color: 1, role: 1, name: 1, connectionStatus: 1, + color: 1, + role: 1, + name: 1, + avatar: 1, + connectionStatus: 1, }, }); const { color, role, name, + avatar, connectionStatus, } = sender; @@ -62,6 +69,7 @@ const mapGroupMessage = (message) => { color, isModerator: role === ROLE_MODERATOR, name, + avatar, isOnline: connectionStatus === CONNECTION_STATUS_ONLINE, }; @@ -83,11 +91,15 @@ const reduceGroupMessages = (previous, current) => { return previous.concat(currentMessage); } // Check if the last message is from the same user and time discrepancy - // between the two messages exceeds window and then group current message - // with the last one + // between the two messages exceeds window and then group current + // message with the last one const timeOfLastMessage = lastMessage.content[lastMessage.content.length - 1].time; + const isOrWasPoll = currentMessage.message.includes(POLL_MESSAGE_PREFIX) + || lastMessage.message.includes(POLL_MESSAGE_PREFIX); + const groupingWindow = isOrWasPoll ? 0 : GROUPING_MESSAGES_WINDOW; + if (lastMessage.sender === currentMessage.sender - && (currentMessage.timestamp - timeOfLastMessage) <= GROUPING_MESSAGES_WINDOW) { + && (currentMessage.timestamp - timeOfLastMessage) <= groupingWindow) { lastMessage.content.push(currentMessage.content.pop()); return previous; } diff --git a/bigbluebutton-html5/imports/ui/components/chat/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/styles.scss index edeb118334113078677c7da59799f485b0a660c9..553e970cdb5acb9c0bc44d5f0347fc2dd26a45d7 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/chat/styles.scss @@ -1,5 +1,6 @@ @import "/imports/ui/stylesheets/mixins/focus"; -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/breakpoints"; +@import "/imports/ui/stylesheets/variables/placeholders"; :root { --toast-content-width: 98%; diff --git a/bigbluebutton-html5/imports/ui/components/checkbox/styles.scss b/bigbluebutton-html5/imports/ui/components/checkbox/styles.scss index 8abc46e2ffd0108ec8553fa3e7be275b07396ea2..1cbfda52f4cbbd056df568eb8ac594428be8e64b 100644 --- a/bigbluebutton-html5/imports/ui/components/checkbox/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/checkbox/styles.scss @@ -1,5 +1,3 @@ -@import "../../stylesheets/variables/_all"; - .input { border: 0; clip: rect(0 0 0 0); diff --git a/bigbluebutton-html5/imports/ui/components/connection-status/modal/component.jsx b/bigbluebutton-html5/imports/ui/components/connection-status/modal/component.jsx index a1c93cd31aa0fc8f6f7b1ed2e32fd16cc1d1edf7..0430f03ae82dfc9641bb6eef397b09c38ecce3a2 100644 --- a/bigbluebutton-html5/imports/ui/components/connection-status/modal/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/connection-status/modal/component.jsx @@ -84,8 +84,9 @@ class ConnectionStatusComponent extends PureComponent { <div className={styles.left}> <div className={styles.avatar}> <UserAvatar - className={styles.icon} + className={cx({ [styles.initials]: conn.avatar.length === 0 })} you={conn.you} + avatar={conn.avatar} moderator={conn.moderator} color={conn.color} > diff --git a/bigbluebutton-html5/imports/ui/components/connection-status/modal/styles.scss b/bigbluebutton-html5/imports/ui/components/connection-status/modal/styles.scss index 5bbd7634eecc413578a34941a730e19cf7cdf6d0..f273cdf85b940a85e7f21b000774c68ffdd35e0b 100644 --- a/bigbluebutton-html5/imports/ui/components/connection-status/modal/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/connection-status/modal/styles.scss @@ -1,13 +1,5 @@ -@import '/imports/ui/stylesheets/mixins/focus'; -@import '/imports/ui/stylesheets/variables/_all'; @import "/imports/ui/components/modal/simple/styles"; -:root { - --modal-margin: 3rem; - --title-position-left: 2.2rem; - --closeBtn-position-left: 2.5rem; -} - .title { left: var(--title-position-left); right: auto; @@ -90,7 +82,7 @@ justify-content: center; align-items: center; - .icon { + .initials { min-width: 2.25rem; height: 2.25rem; } diff --git a/bigbluebutton-html5/imports/ui/components/connection-status/service.js b/bigbluebutton-html5/imports/ui/components/connection-status/service.js index fd6445e05c5ec10c0f248caaef526d2da149619b..feb7b6b578f853a46fd3cad1e7ea0f97877cf761 100644 --- a/bigbluebutton-html5/imports/ui/components/connection-status/service.js +++ b/bigbluebutton-html5/imports/ui/components/connection-status/service.js @@ -87,6 +87,7 @@ const getConnectionStatus = () => { userId: 1, name: 1, role: 1, + avatar: 1, color: 1, connectionStatus: 1, }, @@ -96,6 +97,7 @@ const getConnectionStatus = () => { userId, name, role, + avatar, color, connectionStatus: userStatus, } = user; @@ -105,6 +107,7 @@ const getConnectionStatus = () => { if (status) { result.push({ name, + avatar, offline: userStatus === 'offline', you: Auth.userID === userId, moderator: role === ROLE_MODERATOR, diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx index 4183ae38cff6697594836c9583fea2feff0acafd..13d38270915daedec1ab8c185fc41bb5316d8080 100644 --- a/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx @@ -1,8 +1,10 @@ -import React, { Component } from 'react'; +import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import { findDOMNode } from 'react-dom'; +import { isMobile } from 'react-device-detect'; +import TetherComponent from 'react-tether'; import cx from 'classnames'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import Button from '/imports/ui/components/button/component'; import screenreaderTrap from 'makeup-screenreader-trap'; import { styles } from './styles'; @@ -16,7 +18,7 @@ const intlMessages = defineMessages({ }, }); -const noop = () => {}; +const noop = () => { }; const propTypes = { /** @@ -50,7 +52,8 @@ const propTypes = { onHide: PropTypes.func, onShow: PropTypes.func, autoFocus: PropTypes.bool, - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, + tethered: PropTypes.bool, }; const defaultProps = { @@ -60,6 +63,16 @@ const defaultProps = { autoFocus: false, isOpen: false, keepOpen: null, + getContent: () => {}, +}; + +const attachments = { + 'right-bottom': 'bottom left', + 'right-top': 'bottom left', +}; +const targetAttachments = { + 'right-bottom': 'bottom right', + 'right-top': 'top right', }; class Dropdown extends Component { @@ -72,10 +85,6 @@ class Dropdown extends Component { this.handleWindowClick = this.handleWindowClick.bind(this); } - componentWillUpdate(nextProps, nextState) { - return nextState.isOpen ? screenreaderTrap.trap(this.dropdown) : screenreaderTrap.untrap(); - } - componentDidUpdate(prevProps, prevState) { const { onShow, @@ -85,6 +94,11 @@ class Dropdown extends Component { const { isOpen } = this.state; + if (isOpen) { + screenreaderTrap.trap(this.dropdown); + } else { + screenreaderTrap.untrap(); + } if (isOpen && !prevState.isOpen) { onShow(); } @@ -162,10 +176,24 @@ class Dropdown extends Component { className, intl, keepOpen, + tethered, + placement, + getContent, ...otherProps } = this.props; const { isOpen } = this.state; + + const placements = placement && placement.replace(' ', '-'); + const test = isMobile ? { + width: '100%', + height: '100%', + transform: 'translateY(0)', + } : { + width: '', + height: '', + transform: '', + }; let trigger = children.find(x => x.type === DropdownTrigger); let content = children.find(x => x.type === DropdownContent); @@ -176,15 +204,20 @@ class Dropdown extends Component { dropdownToggle: this.handleToggle, dropdownShow: this.handleShow, dropdownHide: this.handleHide, + keepopen: `${keepOpen}`, }); content = React.cloneElement(content, { - ref: (ref) => { this.content = ref; }, + ref: (ref) => { + getContent(ref); + this.content = ref; + }, 'aria-expanded': isOpen, dropdownIsOpen: isOpen, dropdownToggle: this.handleToggle, dropdownShow: this.handleShow, dropdownHide: this.handleHide, + keepopen: `${keepOpen}`, }); const showCloseBtn = (isOpen && keepOpen) || (isOpen && keepOpen === null); @@ -199,18 +232,67 @@ class Dropdown extends Component { ref={(node) => { this.dropdown = node; }} tabIndex={-1} > - {trigger} - {content} - {showCloseBtn - ? ( - <Button - className={styles.close} - label={intl.formatMessage(intlMessages.close)} - size="lg" - color="default" - onClick={this.handleHide} - /> - ) : null} + { + tethered ? + ( + <TetherComponent + style={{ + zIndex: isOpen ? 15 : '', + ...test, + }} + attachment={ + isMobile ? 'middle bottom' + : attachments[placements] + } + targetAttachment={ + isMobile ? '' + : targetAttachments[placements] + } + constraints={[ + { + to: 'scrollParent', + }, + ]} + renderTarget={ref => ( + <span ref={ref}> + {trigger} + </span>)} + renderElement={ref => ( + <div + ref={ref} + > + {content} + {showCloseBtn + ? ( + <Button + className={styles.close} + label={intl.formatMessage(intlMessages.close)} + size="lg" + color="default" + onClick={this.handleHide} + /> + ) : null} + </div> + ) + } + />) + : ( + <Fragment> + {trigger} + {content} + {showCloseBtn + ? ( + <Button + className={styles.close} + label={intl.formatMessage(intlMessages.close)} + size="lg" + color="default" + onClick={this.handleHide} + /> + ) : null} + </Fragment> + ) + } </div> ); } @@ -218,4 +300,4 @@ class Dropdown extends Component { Dropdown.propTypes = propTypes; Dropdown.defaultProps = defaultProps; -export default injectIntl(Dropdown); +export default injectIntl(Dropdown, { forwardRef: true }); diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/content/component.jsx b/bigbluebutton-html5/imports/ui/components/dropdown/content/component.jsx index 9bcbe4fa6917dce3c5dd12abdf43401a9350446d..f6d38545c89e611f161e5524a148ff505aaf6777 100644 --- a/bigbluebutton-html5/imports/ui/components/dropdown/content/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/dropdown/content/component.jsx @@ -26,8 +26,14 @@ const defaultProps = { export default class DropdownContent extends Component { render() { const { - placement, children, className, - dropdownToggle, dropdownShow, dropdownHide, dropdownIsOpen, + placement, + children, + className, + dropdownToggle, + dropdownShow, + dropdownHide, + dropdownIsOpen, + keepOpen, ...restProps } = this.props; @@ -38,6 +44,7 @@ export default class DropdownContent extends Component { dropdownToggle, dropdownShow, dropdownHide, + keepOpen, })); return ( diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/list/component.jsx b/bigbluebutton-html5/imports/ui/components/dropdown/list/component.jsx index abd09fe45840c6cd4ca50029ab3210699a104481..03310297f2e4846b32ca71bf3c692bbb57e6640b 100755 --- a/bigbluebutton-html5/imports/ui/components/dropdown/list/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/dropdown/list/component.jsx @@ -45,8 +45,8 @@ export default class DropdownList extends Component { } componentDidUpdate() { - const { focusedIndex } = this.state; + const { focusedIndex } = this.state; const children = [].slice.call(this._menu.children); this.menuRefs = children.filter(child => child.getAttribute('role') === 'menuitem'); @@ -126,13 +126,14 @@ export default class DropdownList extends Component { } handleItemClick(event, callback) { - const { getDropdownMenuParent, onActionsHide, dropdownHide } = this.props; - - if (getDropdownMenuParent) { - onActionsHide(); - } else { - this.setState({ focusedIndex: null }); - dropdownHide(); + const { getDropdownMenuParent, onActionsHide, dropdownHide, keepOpen} = this.props; + if(!keepOpen) { + if (getDropdownMenuParent) { + onActionsHide(); + } else { + this.setState({ focusedIndex: null }); + dropdownHide(); + } } if (typeof callback === 'function') { diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/list/styles.scss b/bigbluebutton-html5/imports/ui/components/dropdown/list/styles.scss index d26b835073da3f7539296574a636ce265fac34f7..d7c0392744c75e94bbd8c229e73397ef54aca068 100755 --- a/bigbluebutton-html5/imports/ui/components/dropdown/list/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/dropdown/list/styles.scss @@ -1,5 +1,6 @@ -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/breakpoints"; @import '/imports/ui/stylesheets/mixins/_indicators'; +@import "/imports/ui/stylesheets/variables/placeholders"; %list { list-style: none; @@ -81,7 +82,7 @@ &:hover, &:focus { - @include highContrastOutline(); + @extend %highContrastOutline; cursor: pointer; background-color: var(--color-primary); color: var(--color-white); diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/styles.scss b/bigbluebutton-html5/imports/ui/components/dropdown/styles.scss index 1a6e4ba70413a9fd09706223d7d89357205678f3..f3fa835f4f87711ba6c7ceaec9de81f1e5c845be 100755 --- a/bigbluebutton-html5/imports/ui/components/dropdown/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/dropdown/styles.scss @@ -1,6 +1,7 @@ -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/breakpoints"; @import "/imports/ui/stylesheets/mixins/_scrollable"; @import "/imports/ui/stylesheets/mixins/_indicators"; +@import "/imports/ui/stylesheets/variables/placeholders"; :root { --dropdown-bg: var(--color-white); @@ -23,7 +24,7 @@ } .content { - @include highContrastOutline(); + @extend %highContrastOutline; outline-style: solid; z-index: 9999; position: absolute; diff --git a/bigbluebutton-html5/imports/ui/components/end-meeting-confirmation/component.jsx b/bigbluebutton-html5/imports/ui/components/end-meeting-confirmation/component.jsx index 2b99b2c673a2b824f7555d9692937b4558cc9c6b..3f5f2de10525d8fe218c3222275452ad24cf9b1b 100644 --- a/bigbluebutton-html5/imports/ui/components/end-meeting-confirmation/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/end-meeting-confirmation/component.jsx @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import Button from '/imports/ui/components/button/component'; import Modal from '/imports/ui/components/modal/simple/component'; import { styles } from './styles'; @@ -26,7 +26,7 @@ const intlMessages = defineMessages({ }); const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, closeModal: PropTypes.func.isRequired, endMeeting: PropTypes.func.isRequired, }; diff --git a/bigbluebutton-html5/imports/ui/components/end-meeting-confirmation/styles.scss b/bigbluebutton-html5/imports/ui/components/end-meeting-confirmation/styles.scss index 0b008cd872b20e369b8edab227c5f8342e630936..08277f9c20f5d84ea1a69987a79e68b9db0c8561 100755 --- a/bigbluebutton-html5/imports/ui/components/end-meeting-confirmation/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/end-meeting-confirmation/styles.scss @@ -1,11 +1,5 @@ -@import '/imports/ui/stylesheets/mixins/focus'; -@import '/imports/ui/stylesheets/variables/_all'; @import "/imports/ui/components/modal/simple/styles"; -:root { - --description-margin: 3.5rem; -} - .title { color: var(--color-gray-dark); font-weight: var(--headings-font-weight); diff --git a/bigbluebutton-html5/imports/ui/components/error-screen/component.jsx b/bigbluebutton-html5/imports/ui/components/error-screen/component.jsx index 1ee8c4f7bcaf85badc1742bdd80c08ed8bd9dd05..df1383d4d005d7b194f8c5f9079d34b868bcad28 100644 --- a/bigbluebutton-html5/imports/ui/components/error-screen/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/error-screen/component.jsx @@ -4,6 +4,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import { Meteor } from 'meteor/meteor'; import { Session } from 'meteor/session'; import AudioManager from '/imports/ui/services/audio-manager'; +import logger from '/imports/startup/client/logger'; import { styles } from './styles'; const intlMessages = defineMessages({ @@ -42,8 +43,10 @@ const defaultProps = { class ErrorScreen extends PureComponent { componentDidMount() { + const { code } = this.props; AudioManager.exitAudio(); Meteor.disconnect(); + logger.error({ logCode: 'startup_client_usercouldnotlogin_error' }, `User could not log in HTML5, hit ${code}`); } render() { diff --git a/bigbluebutton-html5/imports/ui/components/error-screen/styles.scss b/bigbluebutton-html5/imports/ui/components/error-screen/styles.scss index 4aef80e9433f860025ecdebd101697350d898c47..b6571fa0db496eff9505d3904ea946017798ed5a 100644 --- a/bigbluebutton-html5/imports/ui/components/error-screen/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/error-screen/styles.scss @@ -1,5 +1,3 @@ -@import "/imports/ui/stylesheets/variables/palette"; - .background { position: fixed; display: flex; diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx b/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx index 306024993c8fec1acf759a2f75da2ce47932c994..ed6cd1829e8170acf5dfa046c5265d5b7020e861 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx @@ -55,7 +55,9 @@ class VideoPlayer extends Component { }, file: { attributes: { - controls: true, + controls: 'controls', + autoplay: 'autoplay', + playsinline: 'playsinline', }, }, dailymotion: { @@ -102,6 +104,7 @@ class VideoPlayer extends Component { componentDidMount() { window.addEventListener('resize', this.resizeListener); + window.addEventListener('layoutSizesSets', this.resizeListener); window.addEventListener('beforeunload', this.onBeforeUnload); clearInterval(this.syncInterval); diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/custom-players/arc-player.jsx b/bigbluebutton-html5/imports/ui/components/external-video-player/custom-players/arc-player.jsx index 0b9e14ef53c9dc69ee344414979be39fa6f5083e..fa1a1920214a8fc004985596ebf74a958a71c6fd 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/custom-players/arc-player.jsx +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/custom-players/arc-player.jsx @@ -1,7 +1,7 @@ import loadScript from 'load-script'; import React, { Component } from 'react' -const MATCH_URL = new RegExp("https?:\/\/(\\w+)\.(instructuremedia.com)(\/embed)?\/([-abcdef0-9]+)"); +const MATCH_URL = new RegExp("https?:\/\/(\\w+)[.](instructuremedia.com)(\/embed)?\/([-abcdef0-9]+)"); const SDK_URL = 'https://files.instructuremedia.com/instructure-media-script/instructure-media-1.1.0.js'; diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/custom-players/panopto.jsx b/bigbluebutton-html5/imports/ui/components/external-video-player/custom-players/panopto.jsx new file mode 100644 index 0000000000000000000000000000000000000000..d7a886631b25c794c995bf31c125a48e9fbaf911 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/custom-players/panopto.jsx @@ -0,0 +1,16 @@ +const MATCH_URL = /https?\:\/\/([^\/]+\/Panopto)(\/Pages\/Viewer\.aspx\?id=)([-a-zA-Z0-9]+)/; + +export class Panopto { + + static canPlay = url => { + return MATCH_URL.test(url) + }; + + static getSocialUrl(url) { + const m = url.match(MATCH_URL); + return 'https://' + m[1] + '/Podcast/Social/' + m[3] + '.mp4'; + } +} + +export default Panopto; + diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/modal/styles.scss b/bigbluebutton-html5/imports/ui/components/external-video-player/modal/styles.scss index 2ca1d27d66394e04eccdbf88823d9a7cdd68688b..7000a00ef03eb7acb6d0bac7e95a6356feacd0e1 100755 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/modal/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/modal/styles.scss @@ -1,4 +1,4 @@ -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/breakpoints"; @import "/imports/ui/components/modal/simple/styles"; @import "/imports/ui/stylesheets/mixins/focus"; diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/service.js b/bigbluebutton-html5/imports/ui/components/external-video-player/service.js index 927a31a53d92d70bff24fb7baa8ff5ac0b271403..66bd3ea7ccc3d2fe70c51db90ce3a4c454af614e 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/service.js +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/service.js @@ -8,10 +8,19 @@ import { makeCall } from '/imports/ui/services/api'; import ReactPlayer from 'react-player'; -const isUrlValid = url => ReactPlayer.canPlay(url); +import Panopto from './custom-players/panopto'; + +const isUrlValid = (url) => { + return ReactPlayer.canPlay(url) || Panopto.canPlay(url); +} const startWatching = (url) => { - const externalVideoUrl = url; + let externalVideoUrl = url; + + if (Panopto.canPlay(url)) { + externalVideoUrl = Panopto.getSocialUrl(url); + } + makeCall('startWatchingExternalVideo', { externalVideoUrl }); }; diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/styles.scss b/bigbluebutton-html5/imports/ui/components/external-video-player/styles.scss index 1b888e2bb3c2b134d96c496a846695d9ef7be08c..3b4974c572b2dab8fe1ef8c177af85a19f74c47f 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/styles.scss @@ -1,6 +1,3 @@ -@import "/imports/ui/stylesheets/mixins/focus"; -@import "/imports/ui/stylesheets/variables/_all"; - .videoPlayer iframe { display: flex; flex-flow: column; @@ -24,5 +21,4 @@ vertical-align: middle; text-align: center; pointer-events: none; - } diff --git a/bigbluebutton-html5/imports/ui/components/fallback-errors/fallback-presentation/styles.scss b/bigbluebutton-html5/imports/ui/components/fallback-errors/fallback-presentation/styles.scss index 3651c36225a0edd078062c8fb3073f277bff6b13..ac92e56377a455f2cc916fbfbd215e49e94a2e47 100644 --- a/bigbluebutton-html5/imports/ui/components/fallback-errors/fallback-presentation/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/fallback-errors/fallback-presentation/styles.scss @@ -1,5 +1,3 @@ -@import "/imports/ui/stylesheets/variables/palette"; - .background { display: flex; flex-flow: column; diff --git a/bigbluebutton-html5/imports/ui/components/fullscreen-button/component.jsx b/bigbluebutton-html5/imports/ui/components/fullscreen-button/component.jsx index e157efc3522f26d645eca194ea4341f0ba923501..ebceb4b9fd31910202f6fb730730ccfb0b74d1ab 100755 --- a/bigbluebutton-html5/imports/ui/components/fullscreen-button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/fullscreen-button/component.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import Button from '/imports/ui/components/button/component'; import cx from 'classnames'; import PropTypes from 'prop-types'; @@ -13,7 +13,7 @@ const intlMessages = defineMessages({ }); const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, fullscreenRef: PropTypes.instanceOf(Element), dark: PropTypes.bool, bottom: PropTypes.bool, diff --git a/bigbluebutton-html5/imports/ui/components/fullscreen-button/styles.scss b/bigbluebutton-html5/imports/ui/components/fullscreen-button/styles.scss index d9b747ae8459cd1ca8752368bf3298f3d0b387f6..c959d8aeec471541372fdd88625d86d9567665ca 100644 --- a/bigbluebutton-html5/imports/ui/components/fullscreen-button/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/fullscreen-button/styles.scss @@ -1,11 +1,3 @@ -@import '/imports/ui/stylesheets/variables/_all'; - -:root { - ::-webkit-media-controls { - display: none !important; - } -} - .wrapper { position: absolute; right: 0; @@ -13,7 +5,6 @@ background-color: var(--color-transparent); cursor: pointer; border: 0; - border-radius: 5px; z-index: 2; [dir="rtl"] & { @@ -76,4 +67,4 @@ i { font-size: 1rem; } -} \ No newline at end of file +} diff --git a/bigbluebutton-html5/imports/ui/components/join-handler/component.jsx b/bigbluebutton-html5/imports/ui/components/join-handler/component.jsx index 3bb0d608fb7e7a86fcdb86002a9ac6b935a2c1d3..941c3a7e646cc2a34ae94a6ccb7a164f2da012cc 100755 --- a/bigbluebutton-html5/imports/ui/components/join-handler/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/join-handler/component.jsx @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import { Session } from 'meteor/session'; import PropTypes from 'prop-types'; +import SanitizeHTML from 'sanitize-html'; import Auth from '/imports/ui/services/auth'; import { setCustomLogoUrl, setModeratorOnlyMessage } from '/imports/ui/components/user-list/service'; import { makeCall } from '/imports/ui/services/api'; @@ -141,7 +142,15 @@ class JoinHandler extends Component { const setModOnlyMessage = (resp) => { if (resp && resp.modOnlyMessage) { - setModeratorOnlyMessage(resp.modOnlyMessage); + const sanitizedModOnlyText = SanitizeHTML(resp.modOnlyMessage, { + allowedTags: ['a', 'b', 'br', 'i', 'img', 'li', 'small', 'span', 'strong', 'u', 'ul'], + allowedAttributes: { + a: ['href', 'name', 'target'], + img: ['src', 'width', 'height'], + }, + allowedSchemes: ['https'], + }); + setModeratorOnlyMessage(sanitizedModOnlyText); } return resp; }; diff --git a/bigbluebutton-html5/imports/ui/components/layout/context.jsx b/bigbluebutton-html5/imports/ui/components/layout/context.jsx new file mode 100644 index 0000000000000000000000000000000000000000..4a83c894f3d5725452d866a9a88020af95dd621d --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/layout/context.jsx @@ -0,0 +1,290 @@ +import React, { createContext, useReducer, useEffect } from 'react'; +import Storage from '/imports/ui/services/storage/session'; + +const { webcamsDefaultPlacement } = Meteor.settings.public.layout; + +export const LayoutContext = createContext(); + +const initialState = { + autoArrangeLayout: true, + webcamsAreaResizing: false, + numUsersVideo: null, + windowSize: { + width: 0, + height: 0, + }, + mediaBounds: { + width: 0, + height: 0, + top: 0, + left: 0, + }, + userListSize: { + width: 0, + }, + chatSize: { + width: 0, + }, + noteSize: { + width: 0, + }, + captionsSize: { + width: 0, + }, + pollSize: { + width: 0, + }, + waitingSize: { + width: 0, + }, + breakoutRoomSize: { + width: 0, + }, + webcamsAreaSize: { + width: 0, + height: 0, + }, + tempWebcamsAreaSize: { + width: 0, + height: 0, + }, + webcamsAreaUserSetsHeight: 0, + webcamsAreaUserSetsWidth: 0, + webcamsPlacement: webcamsDefaultPlacement || 'top', + presentationAreaSize: { + width: 0, + height: 0, + }, + presentationSlideSize: { + width: 0, + height: 0, + }, + presentationIsFullscreen: null, + presentationOrientation: null, +}; + +const reducer = (state, action) => { + switch (action.type) { + case 'setAutoArrangeLayout': { + return { + ...state, + autoArrangeLayout: action.value, + }; + } + case 'setWebcamsAreaResizing': { + return { + ...state, + webcamsAreaResizing: action.value, + }; + } + case 'setUsersVideo': { + return { + ...state, + numUsersVideo: action.value, + }; + } + case 'setWindowSize': { + return { + ...state, + windowSize: { + width: action.value.width, + height: action.value.height, + }, + }; + } + case 'setMediaBounds': { + return { + ...state, + mediaBounds: { + width: action.value.width, + height: action.value.height, + top: action.value.top, + left: action.value.left, + }, + }; + } + case 'setUserListSize': { + return { + ...state, + userListSize: { + width: action.value.width, + }, + }; + } + case 'setChatSize': { + return { + ...state, + chatSize: { + width: action.value.width, + }, + }; + } + case 'setNoteSize': { + return { + ...state, + noteSize: { + width: action.value.width, + }, + }; + } + case 'setCaptionsSize': { + return { + ...state, + captionsSize: { + width: action.value.width, + }, + }; + } + case 'setPollSize': { + return { + ...state, + pollSize: { + width: action.value.width, + }, + }; + } + case 'setWaitingUsersPanelSize': { + return { + ...state, + waitingSize: { + width: action.value.width, + }, + }; + } + case 'setBreakoutRoomSize': { + return { + ...state, + breakoutRoomSize: { + width: action.value.width, + }, + }; + } + case 'setWebcamsPlacement': { + // webcamsPlacement: ('top' | 'right' | 'bottom' | 'left') string + return { + ...state, + webcamsPlacement: action.value, + }; + } + case 'setWebcamsAreaSize': { + return { + ...state, + webcamsAreaSize: { + width: action.value.width, + height: action.value.height, + }, + }; + } + case 'setTempWebcamsAreaSize': { + return { + ...state, + tempWebcamsAreaSize: { + width: action.value.width, + height: action.value.height, + }, + }; + } + case 'setWebcamsAreaUserSetsHeight': { + return { + ...state, + webcamsAreaUserSetsHeight: action.value, + }; + } + case 'setWebcamsAreaUserSetsWidth': { + return { + ...state, + webcamsAreaUserSetsWidth: action.value, + }; + } + case 'setPresentationAreaSize': { + return { + ...state, + presentationAreaSize: { + width: action.value.width, + height: action.value.height, + }, + }; + } + case 'setPresentationSlideSize': { + return { + ...state, + presentationSlideSize: { + width: action.value.width, + height: action.value.height, + }, + }; + } + case 'setPresentationFullscreen': { + // presentationIsFullscreen: (true | false) boolean + return { + ...state, + presentationIsFullscreen: action.value, + }; + } + case 'setPresentationOrientation': { + // presentationOrientation: ('portrait' | 'landscape') string + return { + ...state, + presentationOrientation: action.value, + }; + } + default: { + throw new Error('Unexpected action'); + } + } +}; + +const ContextProvider = (props) => { + const [layoutContextState, layoutContextDispatch] = useReducer(reducer, initialState); + const { + webcamsPlacement, + webcamsAreaUserSetsHeight, + webcamsAreaUserSetsWidth, + autoArrangeLayout, + } = layoutContextState; + const { children } = props; + + useEffect(() => { + Storage.setItem('webcamsPlacement', webcamsPlacement); + Storage.setItem('webcamsAreaUserSetsHeight', webcamsAreaUserSetsHeight); + Storage.setItem('webcamsAreaUserSetsWidth', webcamsAreaUserSetsWidth); + Storage.setItem('autoArrangeLayout', autoArrangeLayout); + }, [ + webcamsPlacement, + webcamsAreaUserSetsHeight, + webcamsAreaUserSetsWidth, + autoArrangeLayout, + ]); + + return ( + <LayoutContext.Provider value={{ + layoutContextState, + layoutContextDispatch, + ...props, + }} + > + {children} + </LayoutContext.Provider> + ); +}; + +const withProvider = Component => props => ( + <ContextProvider {...props}> + <Component /> + </ContextProvider> +); + +const ContextConsumer = Component => props => ( + <LayoutContext.Consumer> + {contexts => <Component {...props} {...contexts} />} + </LayoutContext.Consumer> +); + +const withLayoutConsumer = Component => ContextConsumer(Component); +const withLayoutContext = Component => withProvider(withLayoutConsumer(Component)); + +export { + withProvider, + withLayoutConsumer, + withLayoutContext, +}; diff --git a/bigbluebutton-html5/imports/ui/components/layout/layout-manager.jsx b/bigbluebutton-html5/imports/ui/components/layout/layout-manager.jsx new file mode 100644 index 0000000000000000000000000000000000000000..1d97e9dd59ae8c9e242d19b78e14f0e46f67eef5 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/layout/layout-manager.jsx @@ -0,0 +1,565 @@ +import React, { Component, Fragment } from 'react'; +import Storage from '/imports/ui/services/storage/session'; +import { Session } from 'meteor/session'; +import { withLayoutConsumer } from '/imports/ui/components/layout/context'; +import { isVideoBroadcasting } from '/imports/ui/components/screenshare/service'; +import _ from 'lodash'; + +const windowWidth = () => window.innerWidth; +const windowHeight = () => window.innerHeight; +const min = (value1, value2) => (value1 <= value2 ? value1 : value2); +const max = (value1, value2) => (value1 >= value2 ? value1 : value2); + +// values based on sass file +const USERLIST_MIN_WIDTH = 150; +const USERLIST_MAX_WIDTH = 240; +const CHAT_MIN_WIDTH = 150; +const CHAT_MAX_WIDTH = 335; +const NAVBAR_HEIGHT = 85; +const ACTIONSBAR_HEIGHT = 42; + +const WEBCAMSAREA_MIN_PERCENT = 0.2; +const WEBCAMSAREA_MAX_PERCENT = 0.8; +// const PRESENTATIONAREA_MIN_PERCENT = 0.2; +const PRESENTATIONAREA_MIN_WIDTH = 385; // Value based on presentation toolbar +// const PRESENTATIONAREA_MAX_PERCENT = 0.8; + +const storageLayoutData = () => Storage.getItem('layoutData'); + +class LayoutManager extends Component { + static calculatesPresentationSize( + mediaAreaWidth, mediaAreaHeight, presentationSlideWidth, presentationSlideHeight, + ) { + let presentationWidth; + let presentationHeight; + if (presentationSlideWidth > presentationSlideHeight + || presentationSlideWidth === presentationSlideHeight) { + presentationWidth = mediaAreaWidth; + presentationHeight = (mediaAreaWidth * presentationSlideHeight) + / presentationSlideWidth; + // if overflow + if (presentationHeight > mediaAreaHeight) { + presentationWidth = (mediaAreaHeight * presentationWidth) / presentationHeight; + presentationHeight = mediaAreaHeight; + } + } + if (presentationSlideHeight > presentationSlideWidth) { + presentationWidth = (mediaAreaHeight * presentationSlideWidth) + / presentationSlideHeight; + presentationHeight = mediaAreaHeight; + // if overflow + if (presentationWidth > mediaAreaWidth) { + presentationHeight = (mediaAreaWidth * presentationWidth) / presentationHeight; + presentationWidth = mediaAreaWidth; + } + } + + return { + presentationWidth, + presentationHeight, + }; + } + + constructor(props) { + super(props); + + this.setLayoutSizes = this.setLayoutSizes.bind(this); + this.calculatesLayout = this.calculatesLayout.bind(this); + } + + componentDidMount() { + this.setLayoutSizes(); + window.addEventListener('resize', _.throttle(() => this.setLayoutSizes(), 200)); + + window.addEventListener('panelChanged', () => { + this.setLayoutSizes(true); + }); + + window.addEventListener('autoArrangeChanged', () => { + setTimeout(() => this.setLayoutSizes(false, true), 200); + }); + + window.addEventListener('slideChanged', () => { + setTimeout(() => this.setLayoutSizes(), 200); + }); + + window.addEventListener('togglePresentationHide', () => { + setTimeout(() => this.setLayoutSizes(), 200); + }); + + window.addEventListener('webcamAreaResize', () => { + this.setLayoutSizes(); + }); + + window.addEventListener('webcamPlacementChange', () => { + this.setLayoutSizes(false, false, true); + }); + } + + componentDidUpdate(prevProps) { + const { layoutContextState } = this.props; + const { layoutContextState: prevLayoutContextState } = prevProps; + const { + numUsersVideo, + } = layoutContextState; + const { + numUsersVideo: prevNumUsersVideo, + } = prevLayoutContextState; + + if (numUsersVideo !== prevNumUsersVideo) { + setTimeout(() => this.setLayoutSizes(), 500); + } + } + + setLayoutSizes(panelChanged = false, autoarrangeChanged = false, placementChanged = false) { + const { layoutContextDispatch, layoutContextState } = this.props; + const { autoArrangeLayout } = layoutContextState; + + if (autoarrangeChanged && !autoArrangeLayout && !placementChanged) return; + + const layoutSizes = this.calculatesLayout(panelChanged); + + layoutContextDispatch( + { + type: 'setWindowSize', + value: { + width: windowWidth(), + height: windowHeight(), + }, + }, + ); + layoutContextDispatch( + { + type: 'setMediaBounds', + value: { + width: layoutSizes.mediaBounds.width, + height: layoutSizes.mediaBounds.height, + top: layoutSizes.mediaBounds.top, + left: layoutSizes.mediaBounds.left, + }, + }, + ); + layoutContextDispatch( + { + type: 'setUserListSize', + value: { + width: layoutSizes.userListSize.width, + }, + }, + ); + layoutContextDispatch( + { + type: 'setChatSize', + value: { + width: layoutSizes.chatSize.width, + }, + }, + ); + layoutContextDispatch( + { + type: 'setBreakoutRoomSize', + value: { + width: layoutSizes.breakoutRoomSize.width, + }, + }, + ); + layoutContextDispatch( + { + type: 'setWebcamsAreaSize', + value: { + width: layoutSizes.webcamsAreaSize.width, + height: layoutSizes.webcamsAreaSize.height, + }, + }, + ); + layoutContextDispatch( + { + type: 'setPresentationAreaSize', + value: { + width: layoutSizes.presentationAreaSize.width, + height: layoutSizes.presentationAreaSize.height, + }, + }, + ); + + const newLayoutData = { + windowSize: { + width: windowWidth(), + height: windowHeight(), + }, + mediaBounds: { + width: layoutSizes.mediaBounds.width, + height: layoutSizes.mediaBounds.height, + top: layoutSizes.mediaBounds.top, + left: layoutSizes.mediaBounds.left, + }, + userListSize: { + width: layoutSizes.userListSize.width, + }, + chatSize: { + width: layoutSizes.chatSize.width, + }, + breakoutRoomSize: { + width: layoutSizes.breakoutRoomSize.width, + }, + webcamsAreaSize: { + width: layoutSizes.webcamsAreaSize.width, + height: layoutSizes.webcamsAreaSize.height, + }, + presentationAreaSize: { + width: layoutSizes.presentationAreaSize.width, + height: layoutSizes.presentationAreaSize.height, + }, + }; + + Storage.setItem('layoutData', newLayoutData); + window.dispatchEvent(new Event('layoutSizesSets')); + } + + defineWebcamPlacement(mediaAreaWidth, mediaAreaHeight, presentationWidth, presentationHeight) { + const { layoutContextDispatch, layoutContextState } = this.props; + const { autoArrangeLayout } = layoutContextState; + const isScreenShare = isVideoBroadcasting(); + + if (!autoArrangeLayout) return; + + if (isScreenShare) { + layoutContextDispatch( + { + type: 'setWebcamsPlacement', + value: 'top', + }, + ); + Storage.setItem('webcamsPlacement', 'top'); + return; + } + + if ((mediaAreaWidth - presentationWidth) > (mediaAreaHeight - presentationHeight)) { + layoutContextDispatch( + { + type: 'setWebcamsPlacement', + value: 'left', + }, + ); + Storage.setItem('webcamsPlacement', 'left'); + } else { + layoutContextDispatch( + { + type: 'setWebcamsPlacement', + value: 'top', + }, + ); + Storage.setItem('webcamsPlacement', 'top'); + } + } + + calculatesPanelsSize(panelChanged) { + const { layoutContextState } = this.props; + const { + userListSize: userListSizeContext, + chatSize: chatSizeContext, + breakoutRoomSize: breakoutRoomSizeContext, + } = layoutContextState; + const openPanel = Session.get('openPanel'); + const storageLData = storageLayoutData(); + + let storageUserListWidth; + let storageChatWidth; + let storageBreakoutRoomWidth; + if (storageLData) { + storageUserListWidth = storageLData.userListSize.width; + storageChatWidth = storageLData.chatSize.width; + storageBreakoutRoomWidth = storageLData.breakoutRoomSize.width; + } + + let newUserListSize; + let newChatSize; + let newBreakoutRoomSize; + + if (panelChanged && userListSizeContext.width !== 0) { + newUserListSize = userListSizeContext; + } else if (!storageUserListWidth) { + newUserListSize = { + width: min(max((windowWidth() * 0.1), USERLIST_MIN_WIDTH), USERLIST_MAX_WIDTH), + }; + } else { + newUserListSize = { + width: storageUserListWidth, + }; + } + + if (panelChanged && chatSizeContext.width !== 0) { + newChatSize = chatSizeContext; + } else if (!storageChatWidth) { + newChatSize = { + width: min(max((windowWidth() * 0.2), CHAT_MIN_WIDTH), CHAT_MAX_WIDTH), + }; + } else { + newChatSize = { + width: storageChatWidth, + }; + } + + if (panelChanged && breakoutRoomSizeContext.width !== 0) { + newBreakoutRoomSize = breakoutRoomSizeContext; + } else if (!storageBreakoutRoomWidth) { + newBreakoutRoomSize = { + width: min(max((windowWidth() * 0.2), CHAT_MIN_WIDTH), CHAT_MAX_WIDTH), + }; + } else { + newBreakoutRoomSize = { + width: storageBreakoutRoomWidth, + }; + } + + switch (openPanel) { + case 'userlist': { + newChatSize = { + width: 0, + }; + newBreakoutRoomSize = { + width: 0, + }; + break; + } + case 'chat': { + newBreakoutRoomSize = { + width: 0, + }; + break; + } + case 'breakoutroom': { + newChatSize = { + width: 0, + }; + break; + } + case '': { + newUserListSize = { + width: 0, + }; + newChatSize = { + width: 0, + }; + newBreakoutRoomSize = { + width: 0, + }; + break; + } + default: { + throw new Error('Unexpected openPanel value'); + } + } + + return { + newUserListSize, + newChatSize, + newBreakoutRoomSize, + }; + } + + calculatesWebcamsAreaSize( + mediaAreaWidth, mediaAreaHeight, presentationWidth, presentationHeight, + ) { + const { + layoutContextState, + } = this.props; + const { webcamsPlacement, numUsersVideo } = layoutContextState; + + const autoArrangeLayout = Storage.getItem('autoArrangeLayout'); + const webcamsAreaUserSetsHeight = Storage.getItem('webcamsAreaUserSetsHeight'); + const webcamsAreaUserSetsWidth = Storage.getItem('webcamsAreaUserSetsWidth'); + + let webcamsAreaWidth; + let webcamsAreaHeight; + + if (numUsersVideo < 1) { + return { + webcamsAreaWidth: 0, + webcamsAreaHeight: 0, + }; + } + + if (autoArrangeLayout) { + if (webcamsPlacement === 'left' || webcamsPlacement === 'right') { + webcamsAreaWidth = (mediaAreaWidth - presentationWidth) + < (mediaAreaWidth * WEBCAMSAREA_MIN_PERCENT) + ? mediaAreaWidth * WEBCAMSAREA_MIN_PERCENT + : mediaAreaWidth - presentationWidth; + webcamsAreaHeight = mediaAreaHeight; + } else { + webcamsAreaWidth = mediaAreaWidth; + webcamsAreaHeight = (mediaAreaHeight - presentationHeight) + < (mediaAreaHeight * WEBCAMSAREA_MIN_PERCENT) + ? mediaAreaHeight * WEBCAMSAREA_MIN_PERCENT + : mediaAreaHeight - presentationHeight; + } + } else if (webcamsPlacement === 'left' || webcamsPlacement === 'right') { + webcamsAreaWidth = min( + max( + webcamsAreaUserSetsWidth + || mediaAreaWidth * WEBCAMSAREA_MIN_PERCENT, + mediaAreaWidth * WEBCAMSAREA_MIN_PERCENT, + ), + mediaAreaWidth * WEBCAMSAREA_MAX_PERCENT, + ); + webcamsAreaHeight = mediaAreaHeight; + } else { + webcamsAreaWidth = mediaAreaWidth; + webcamsAreaHeight = min( + max( + webcamsAreaUserSetsHeight + || mediaAreaHeight * WEBCAMSAREA_MIN_PERCENT, + mediaAreaHeight * WEBCAMSAREA_MIN_PERCENT, + ), + mediaAreaHeight * WEBCAMSAREA_MAX_PERCENT, + ); + } + + if ((webcamsPlacement === 'left' || webcamsPlacement === 'right') && (mediaAreaWidth - webcamsAreaWidth) < PRESENTATIONAREA_MIN_WIDTH) { + webcamsAreaWidth = mediaAreaWidth - PRESENTATIONAREA_MIN_WIDTH; + } + + return { + webcamsAreaWidth, + webcamsAreaHeight, + }; + } + + calculatesPresentationAreaSize( + mediaAreaWidth, mediaAreaHeight, webcamAreaWidth, webcamAreaHeight, + ) { + const { + layoutContextState, + } = this.props; + const { + webcamsPlacement, + numUsersVideo, + } = layoutContextState; + + if (numUsersVideo < 1) { + return { + presentationAreaWidth: mediaAreaWidth, + presentationAreaHeight: mediaAreaHeight - 20, + }; + } + + let presentationAreaWidth; + let presentationAreaHeight; + + if (webcamsPlacement === 'left' || webcamsPlacement === 'right') { + presentationAreaWidth = mediaAreaWidth - webcamAreaWidth - 20; + presentationAreaHeight = mediaAreaHeight - 20; + } else { + presentationAreaWidth = mediaAreaWidth; + presentationAreaHeight = mediaAreaHeight - webcamAreaHeight - 30; + } + + return { + presentationAreaWidth, + presentationAreaHeight, + }; + } + + calculatesLayout(panelChanged = false) { + const { + layoutContextState, + } = this.props; + const { + presentationIsFullscreen, + presentationSlideSize, + } = layoutContextState; + + const { + width: presentationSlideWidth, + height: presentationSlideHeight, + } = presentationSlideSize; + + const panelsSize = this.calculatesPanelsSize(panelChanged); + + const { + newUserListSize, + newChatSize, + newBreakoutRoomSize, + } = panelsSize; + + const firstPanel = newUserListSize; + let secondPanel = { + width: 0, + }; + if (newChatSize.width > 0) { + secondPanel = newChatSize; + } else if (newBreakoutRoomSize.width > 0) { + secondPanel = newBreakoutRoomSize; + } + + const mediaAreaHeight = windowHeight() - (NAVBAR_HEIGHT + ACTIONSBAR_HEIGHT) - 10; + const mediaAreaWidth = windowWidth() - (firstPanel.width + secondPanel.width); + const newMediaBounds = { + width: mediaAreaWidth, + height: mediaAreaHeight, + top: NAVBAR_HEIGHT, + left: firstPanel.width + secondPanel.width, + }; + + const { presentationWidth, presentationHeight } = LayoutManager.calculatesPresentationSize( + mediaAreaWidth, mediaAreaHeight, presentationSlideWidth, presentationSlideHeight, + ); + + this.defineWebcamPlacement( + mediaAreaWidth, mediaAreaHeight, presentationWidth, presentationHeight, + ); + + const { webcamsAreaWidth, webcamsAreaHeight } = this.calculatesWebcamsAreaSize( + mediaAreaWidth, mediaAreaHeight, presentationWidth, presentationHeight, + ); + + const newWebcamsAreaSize = { + width: webcamsAreaWidth, + height: webcamsAreaHeight, + }; + let newPresentationAreaSize; + let newScreenShareAreaSize; + const { presentationAreaWidth, presentationAreaHeight } = this.calculatesPresentationAreaSize( + mediaAreaWidth, mediaAreaHeight, webcamsAreaWidth, webcamsAreaHeight, + ); + if (!presentationIsFullscreen) { + newPresentationAreaSize = { + width: presentationAreaWidth || 0, + height: presentationAreaHeight || 0, + }; + } else { + newPresentationAreaSize = { + width: windowWidth(), + height: windowHeight(), + }; + } + + return { + mediaBounds: newMediaBounds, + userListSize: newUserListSize, + chatSize: newChatSize, + breakoutRoomSize: newBreakoutRoomSize, + webcamsAreaSize: newWebcamsAreaSize, + presentationAreaSize: newPresentationAreaSize, + screenShareAreaSize: newScreenShareAreaSize, + }; + } + + render() { + return <Fragment />; + } +} + +export default withLayoutConsumer(LayoutManager); +export { + USERLIST_MIN_WIDTH, + USERLIST_MAX_WIDTH, + CHAT_MIN_WIDTH, + CHAT_MAX_WIDTH, + NAVBAR_HEIGHT, + ACTIONSBAR_HEIGHT, + WEBCAMSAREA_MIN_PERCENT, + WEBCAMSAREA_MAX_PERCENT, + PRESENTATIONAREA_MIN_WIDTH, +}; diff --git a/bigbluebutton-html5/imports/ui/components/legacy/component.jsx b/bigbluebutton-html5/imports/ui/components/legacy/component.jsx index 43ee7be52995f08a1c7d0ec03c4ff3f5512c6ce6..6e7c4e662aac9c69435d8656c3792f68942923a7 100755 --- a/bigbluebutton-html5/imports/ui/components/legacy/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/legacy/component.jsx @@ -5,65 +5,65 @@ import './styles.css'; // currently supported locales. -import ar from 'react-intl/locale-data/ar'; -import bg from 'react-intl/locale-data/bg'; -import cs from 'react-intl/locale-data/cs'; -import de from 'react-intl/locale-data/de'; -import el from 'react-intl/locale-data/el'; -import en from 'react-intl/locale-data/en'; -import es from 'react-intl/locale-data/es'; -import eu from 'react-intl/locale-data/eu'; -import fa from 'react-intl/locale-data/fa'; -import fi from 'react-intl/locale-data/fi'; -import fr from 'react-intl/locale-data/fr'; -import he from 'react-intl/locale-data/he'; -import hi from 'react-intl/locale-data/hi'; -import hu from 'react-intl/locale-data/hu'; -import id from 'react-intl/locale-data/id'; -import it from 'react-intl/locale-data/it'; -import ja from 'react-intl/locale-data/ja'; -import km from 'react-intl/locale-data/km'; -import pl from 'react-intl/locale-data/pl'; -import pt from 'react-intl/locale-data/pt'; -import ru from 'react-intl/locale-data/ru'; -import sv from 'react-intl/locale-data/sv'; -import tr from 'react-intl/locale-data/tr'; -import uk from 'react-intl/locale-data/uk'; -import vi from 'react-intl/locale-data/vi'; -import zh from 'react-intl/locale-data/zh'; +// import ar from 'react-intl/locale-data/ar'; +// import bg from 'react-intl/locale-data/bg'; +// import cs from 'react-intl/locale-data/cs'; +// import de from 'react-intl/locale-data/de'; +// import el from 'react-intl/locale-data/el'; +// import en from 'react-intl/locale-data/en'; +// import es from 'react-intl/locale-data/es'; +// import eu from 'react-intl/locale-data/eu'; +// import fa from 'react-intl/locale-data/fa'; +// import fi from 'react-intl/locale-data/fi'; +// import fr from 'react-intl/locale-data/fr'; +// import he from 'react-intl/locale-data/he'; +// import hi from 'react-intl/locale-data/hi'; +// import hu from 'react-intl/locale-data/hu'; +// import id from 'react-intl/locale-data/id'; +// import it from 'react-intl/locale-data/it'; +// import ja from 'react-intl/locale-data/ja'; +// import km from 'react-intl/locale-data/km'; +// import pl from 'react-intl/locale-data/pl'; +// import pt from 'react-intl/locale-data/pt'; +// import ru from 'react-intl/locale-data/ru'; +// import sv from 'react-intl/locale-data/sv'; +// import tr from 'react-intl/locale-data/tr'; +// import uk from 'react-intl/locale-data/uk'; +// import vi from 'react-intl/locale-data/vi'; +// import zh from 'react-intl/locale-data/zh'; -// This class is the only component loaded on legacy (unsupported) browsers. -// What is included here needs to be minimal and carefully considered because some -// things can't be polyfilled. +// // This class is the only component loaded on legacy (unsupported) browsers. +// // What is included here needs to be minimal and carefully considered because some +// // things can't be polyfilled. -addLocaleData([ - ...ar, - ...bg, - ...cs, - ...de, - ...el, - ...en, - ...es, - ...eu, - ...fa, - ...fi, - ...fr, - ...he, - ...hi, - ...hu, - ...id, - ...it, - ...ja, - ...km, - ...pl, - ...pt, - ...ru, - ...sv, - ...tr, - ...uk, - ...vi, - ...zh, -]); +// addLocaleData([ +// ...ar, +// ...bg, +// ...cs, +// ...de, +// ...el, +// ...en, +// ...es, +// ...eu, +// ...fa, +// ...fi, +// ...fr, +// ...he, +// ...hi, +// ...hu, +// ...id, +// ...it, +// ...ja, +// ...km, +// ...pl, +// ...pt, +// ...ru, +// ...sv, +// ...tr, +// ...uk, +// ...vi, +// ...zh, +// ]); const FETCHING = 'fetching'; const FALLBACK = 'fallback'; diff --git a/bigbluebutton-html5/imports/ui/components/loading-screen/styles.scss b/bigbluebutton-html5/imports/ui/components/loading-screen/styles.scss index eeaf0ceade78648807ab84311c3f8b5b82dc06ea..36fded31d3a91d044a5de849717292cd74ce8d24 100644 --- a/bigbluebutton-html5/imports/ui/components/loading-screen/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/loading-screen/styles.scss @@ -1,15 +1,6 @@ -@import "/imports/ui/stylesheets/variables/_all"; - /* Variables * ========== */ - -:root { - --loader-bg: var(--color-gray-dark); - --loader-bullet: var(--color-white); - --loader-message-color: var(--color-white); -} - .background { position: fixed; display: flex; diff --git a/bigbluebutton-html5/imports/ui/components/lock-viewers/styles.scss b/bigbluebutton-html5/imports/ui/components/lock-viewers/styles.scss index 5c0bf0bf1fc2c375642ba1414d4b7b793c57c638..4a3125751bb855298fdac676bb1dd422bc3328a7 100755 --- a/bigbluebutton-html5/imports/ui/components/lock-viewers/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/lock-viewers/styles.scss @@ -1,13 +1,5 @@ -@import '/imports/ui/stylesheets/mixins/focus'; -@import '/imports/ui/stylesheets/variables/_all'; @import "/imports/ui/components/modal/simple/styles"; -:root { - --modal-margin: 3rem; - --title-position-left: 2.2rem; - --closeBtn-position-left: 2.5rem; -} - .title { left: var(--title-position-left); right: auto; diff --git a/bigbluebutton-html5/imports/ui/components/media/autoplay-overlay/styles.scss b/bigbluebutton-html5/imports/ui/components/media/autoplay-overlay/styles.scss index e7bb394b23c1e7627f60f165e28271f9c8c0f66f..4486a2cb710f59143933e50691ae01b2fa031b2c 100644 --- a/bigbluebutton-html5/imports/ui/components/media/autoplay-overlay/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/media/autoplay-overlay/styles.scss @@ -1,5 +1,3 @@ -@import "/imports/ui/stylesheets/variables/_all"; - .autoplayOverlayContent { text-align: center; margin-top: 8px; diff --git a/bigbluebutton-html5/imports/ui/components/media/component.jsx b/bigbluebutton-html5/imports/ui/components/media/component.jsx index 36c17f52feb71b662ac0f8d1cdd798e8a00d1694..9f7c18c054cb076a97eb2bd1b29468b05e865182 100644 --- a/bigbluebutton-html5/imports/ui/components/media/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/media/component.jsx @@ -16,7 +16,7 @@ const propTypes = { swapLayout: PropTypes.bool, disableVideo: PropTypes.bool, audioModalIsOpen: PropTypes.bool, - webcamDraggableState: PropTypes.instanceOf(Object).isRequired, + layoutContextState: PropTypes.instanceOf(Object).isRequired, }; const defaultProps = { @@ -43,12 +43,12 @@ export default class Media extends Component { children, audioModalIsOpen, usersVideo, - webcamDraggableState, + layoutContextState, } = this.props; - const { placement } = webcamDraggableState; - const placementStorage = Storage.getItem('webcamPlacement'); - const webcamPlacement = placement || placementStorage; + const { webcamsPlacement: placement } = layoutContextState; + const placementStorage = Storage.getItem('webcamsPlacement'); + const webcamsPlacement = placement || placementStorage; const contentClassName = cx({ [styles.content]: true, @@ -57,13 +57,13 @@ export default class Media extends Component { const overlayClassName = cx({ [styles.overlay]: true, [styles.hideOverlay]: hideOverlay, - [styles.floatingOverlay]: (webcamPlacement === 'floating'), + [styles.floatingOverlay]: (webcamsPlacement === 'floating'), }); const containerClassName = cx({ [styles.container]: true, - [styles.containerV]: webcamPlacement === 'top' || webcamPlacement === 'bottom' || webcamPlacement === 'floating', - [styles.containerH]: webcamPlacement === 'left' || webcamPlacement === 'right', + [styles.containerV]: webcamsPlacement === 'top' || webcamsPlacement === 'bottom' || webcamsPlacement === 'floating', + [styles.containerH]: webcamsPlacement === 'left' || webcamsPlacement === 'right', }); return ( @@ -77,24 +77,24 @@ export default class Media extends Component { style={{ maxHeight: usersVideo.length > 0 && ( - webcamPlacement !== 'left' - || webcamPlacement !== 'right' + webcamsPlacement !== 'left' + || webcamsPlacement !== 'right' ) && ( - webcamPlacement === 'top' - || webcamPlacement === 'bottom' + webcamsPlacement === 'top' + || webcamsPlacement === 'bottom' ) ? '80%' : '100%', minHeight: BROWSER_ISMOBILE && window.innerWidth > window.innerHeight ? '50%' : '20%', maxWidth: usersVideo.length > 0 && ( - webcamPlacement !== 'top' - || webcamPlacement !== 'bottom' + webcamsPlacement !== 'top' + || webcamsPlacement !== 'bottom' ) && ( - webcamPlacement === 'left' - || webcamPlacement === 'right' + webcamsPlacement === 'left' + || webcamsPlacement === 'right' ) ? '80%' : '100%', diff --git a/bigbluebutton-html5/imports/ui/components/media/container.jsx b/bigbluebutton-html5/imports/ui/components/media/container.jsx index 4efa85853f0a7c769154664a565761f96cf7f4c7..bd70be550bebdf79413ad4fb741c00a2974efd87 100755 --- a/bigbluebutton-html5/imports/ui/components/media/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/media/container.jsx @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import Settings from '/imports/ui/services/settings'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import PropTypes from 'prop-types'; import { Session } from 'meteor/session'; import { notify } from '/imports/ui/services/notification'; @@ -9,20 +9,20 @@ import VideoService from '/imports/ui/components/video-provider/service'; import getFromUserSettings from '/imports/ui/services/users-settings'; import { withModalMounter } from '/imports/ui/components/modal/service'; import Media from './component'; -import MediaService, { getSwapLayout, shouldEnableSwapLayout } from './service'; +import MediaService, { getSwapLayout, shouldEnableSwapLayout } from '/imports/ui/components/media/service'; import PresentationPodsContainer from '../presentation-pod/container'; import ScreenshareContainer from '../screenshare/container'; import DefaultContent from '../presentation/default-content/component'; import ExternalVideoContainer from '../external-video-player/container'; import Storage from '../../services/storage/session'; -import { withDraggableConsumer } from './webcam-draggable-overlay/context'; +import { withLayoutConsumer } from '/imports/ui/components/layout/context'; const LAYOUT_CONFIG = Meteor.settings.public.layout; const KURENTO_CONFIG = Meteor.settings.public.kurento; const propTypes = { isScreensharing: PropTypes.bool.isRequired, - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, }; const intlMessages = defineMessages({ @@ -49,19 +49,22 @@ const intlMessages = defineMessages({ }); class MediaContainer extends Component { - componentWillMount() { + componentDidMount() { document.addEventListener('installChromeExtension', this.installChromeExtension.bind(this)); document.addEventListener('screenshareNotSupported', this.screenshareNotSupported.bind(this)); } - componentWillReceiveProps(nextProps) { + componentDidUpdate(prevProps) { const { isScreensharing, intl, } = this.props; + const { + isScreensharing: wasScreenSharing, + } = prevProps; - if (isScreensharing !== nextProps.isScreensharing) { - if (nextProps.isScreensharing) { + if (isScreensharing !== wasScreenSharing) { + if (wasScreenSharing) { notify(intl.formatMessage(intlMessages.screenshareStarted), 'info', 'desktop'); } else { notify(intl.formatMessage(intlMessages.screenshareEnded), 'info', 'desktop'); @@ -103,11 +106,11 @@ class MediaContainer extends Component { } } -export default withDraggableConsumer(withModalMounter(withTracker(() => { +export default withLayoutConsumer(withModalMounter(withTracker(() => { const { dataSaving } = Settings; const { viewParticipantsWebcams, viewScreenshare } = dataSaving; const hidePresentation = getFromUserSettings('bbb_hide_presentation', LAYOUT_CONFIG.hidePresentation); - const autoSwapLayout = getFromUserSettings('userdata-bbb_auto_swap_layout', LAYOUT_CONFIG.autoSwapLayout); + const autoSwapLayout = getFromUserSettings('bbb_auto_swap_layout', LAYOUT_CONFIG.autoSwapLayout); const { current_presentation: hasPresentation } = MediaService.getPresentationInfo(); const data = { children: <DefaultContent {...{ autoSwapLayout, hidePresentation }} />, @@ -123,7 +126,7 @@ export default withDraggableConsumer(withModalMounter(withTracker(() => { data.children = <ScreenshareContainer />; } - const usersVideo = VideoService.getVideoStreams(); + const { streams: usersVideo } = VideoService.getVideoStreams(); data.usersVideo = usersVideo; if (MediaService.shouldShowOverlay() && usersVideo.length && viewParticipantsWebcams) { @@ -150,7 +153,7 @@ export default withDraggableConsumer(withModalMounter(withTracker(() => { ); } - data.webcamPlacement = Storage.getItem('webcamPlacement'); + data.webcamsPlacement = Storage.getItem('webcamsPlacement'); MediaContainer.propTypes = propTypes; return data; diff --git a/bigbluebutton-html5/imports/ui/components/media/service.js b/bigbluebutton-html5/imports/ui/components/media/service.js index 9811280c05f5fa8669a9789b462962daa23a7818..4a0670f0443b7daa0ad09d926ca4887fcf0714ed 100755 --- a/bigbluebutton-html5/imports/ui/components/media/service.js +++ b/bigbluebutton-html5/imports/ui/components/media/service.js @@ -4,7 +4,6 @@ import { getVideoUrl } from '/imports/ui/components/external-video-player/servic import Auth from '/imports/ui/services/auth'; import Users from '/imports/api/users'; import Settings from '/imports/ui/services/settings'; -import PollingService from '/imports/ui/components/polling/service'; import getFromUserSettings from '/imports/ui/services/users-settings'; const LAYOUT_CONFIG = Meteor.settings.public.layout; @@ -53,6 +52,7 @@ const setSwapLayout = () => { }; const toggleSwapLayout = () => { + window.dispatchEvent(new Event('togglePresentationHide')); swapLayout.value = !swapLayout.value; swapLayout.tracker.changed(); }; diff --git a/bigbluebutton-html5/imports/ui/components/media/styles.scss b/bigbluebutton-html5/imports/ui/components/media/styles.scss index 744ceeeace5842705b9f70ac1670225dd0c1b6ae..85276d4e7aed6331bfea52daf8c38fe12eb8c4da 100644 --- a/bigbluebutton-html5/imports/ui/components/media/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/media/styles.scss @@ -1,7 +1,3 @@ -@import "../../stylesheets/variables/_all"; -@import "../../stylesheets/variables/video"; -@import "../video-provider/video-list/styles"; - $content-order: 2; $before-content-order: 1; $after-content-order: 3; @@ -222,6 +218,52 @@ $after-content-order: 3; } } +@keyframes spin { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(-360deg); + } +} + +.connectingSpinner { + position: absolute; + height: 100%; + width: 100%; + object-fit: contain; + color: var(--color-white-with-transparency); + font-size: 2.5rem; + text-align: center; + white-space: nowrap; + z-index: 1; + + + &::after { + content: ''; + display: inline-block; + height: 100%; + vertical-align: middle; + margin: 0 -0.25em 0 0; + + [dir="rtl"] & { + margin: 0 0 0 -0.25em + } + } + + &::before { + content: "\e949"; + /* ascii code for the ellipsis character */ + font-family: 'bbb-icons' !important; + display: inline-block; + + :global(.animationsEnabled) & { + animation: spin 4s infinite linear; + } + } +} + .overlayToTop span[class^=resizeWrapper], .overlayToBottom span[class^=resizeWrapper] { div { @@ -240,4 +282,4 @@ $after-content-order: 3; z-index: 1; bottom: -10px !important; } -} \ No newline at end of file +} diff --git a/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/component.jsx b/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/component.jsx index 40f82c4ee666aae39e75a241924f05665530a9e4..a47575fcd678a49504151ef6deb05c5d0257c27b 100644 --- a/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/component.jsx @@ -1,7 +1,6 @@ import React, { PureComponent, Fragment } from 'react'; import Draggable from 'react-draggable'; import cx from 'classnames'; -import _ from 'lodash'; import PropTypes from 'prop-types'; import Resizable from 're-resizable'; import { isMobile, isIPad13 } from 'react-device-detect'; @@ -9,6 +8,8 @@ import { withDraggableConsumer } from './context'; import VideoProviderContainer from '/imports/ui/components/video-provider/container'; import { styles } from '../styles.scss'; import Storage from '../../../services/storage/session'; +import { withLayoutConsumer } from '/imports/ui/components/layout/context'; +import { WEBCAMSAREA_MIN_PERCENT, PRESENTATIONAREA_MIN_WIDTH } from '/imports/ui/components/layout/layout-manager'; const BROWSER_ISMOBILE = isMobile || isIPad13; @@ -20,7 +21,8 @@ const propTypes = { webcamDraggableState: PropTypes.objectOf(Object).isRequired, webcamDraggableDispatch: PropTypes.func.isRequired, refMediaContainer: PropTypes.shape({ current: PropTypes.instanceOf(Element) }), - usersVideoLenght: PropTypes.number.isRequired, + layoutContextState: PropTypes.objectOf(Object).isRequired, + layoutContextDispatch: PropTypes.func.isRequired, }; const defaultProps = { @@ -30,78 +32,38 @@ const defaultProps = { audioModalIsOpen: false, refMediaContainer: null, }; -const dispatchResizeEvent = () => window.dispatchEvent(new Event('resize')); class WebcamDraggable extends PureComponent { constructor(props) { super(props); + const { layoutContextState } = props; + const { webcamsPlacement, mediaBounds } = layoutContextState; + this.state = { + webcamsAreaResizable: { + width: webcamsPlacement === 'top' || webcamsPlacement === 'bottom' ? mediaBounds.width : mediaBounds.width * WEBCAMSAREA_MIN_PERCENT, + height: webcamsPlacement === 'left' || webcamsPlacement === 'right' ? mediaBounds.height : mediaBounds.height * WEBCAMSAREA_MIN_PERCENT, + }, + resizing: false, + hideWebcams: false, + }; + this.handleWebcamDragStart = this.handleWebcamDragStart.bind(this); this.handleWebcamDragStop = this.handleWebcamDragStop.bind(this); this.onFullscreenChange = this.onFullscreenChange.bind(this); - this.debouncedOnResize = _.debounce(this.onWindowResize.bind(this), 500); this.onResizeStop = this.onResizeStop.bind(this); this.onResizeStart = this.onResizeStart.bind(this); - this.setPlacementPercent = this.setPlacementPercent.bind(this); - this.recalculateAreaSize = this.recalculateAreaSize.bind(this); - - this.state = { - resizing: false, - placementPercent: 0, - }; + this.handleLayoutSizesSets = this.handleLayoutSizesSets.bind(this); } componentDidMount() { - dispatchResizeEvent(); - window.addEventListener('resize', this.debouncedOnResize); document.addEventListener('fullscreenchange', this.onFullscreenChange); - window.addEventListener('orientationchange', () => setTimeout(this.recalculateAreaSize, 500)); - } - - componentDidUpdate(prevProps) { - const { - swapLayout, - webcamDraggableState, - webcamDraggableDispatch, - usersVideoLenght, - } = this.props; - const { - placement: statePlacement, - orientation, - lastPlacementLandscape, - lastPlacementPortrait, - } = webcamDraggableState; - const { webcamDraggableState: prevWebcamDraggableState } = prevProps; - const { placement: prevPlacement, orientation: prevOrientation } = prevWebcamDraggableState; - if (prevProps.swapLayout !== swapLayout) { - setTimeout(() => this.forceUpdate(), 500); - } - if (prevPlacement !== statePlacement) { - setTimeout(() => this.forceUpdate(), 200); - setTimeout(() => window.dispatchEvent(new Event('resize')), 500); - } - - if (prevProps.usersVideoLenght !== usersVideoLenght) { - dispatchResizeEvent(); - } - - if (prevOrientation !== orientation) { - const storagePlacement = Storage.getItem('webcamPlacement'); - if ((prevOrientation == null || prevOrientation === 'portrait') && orientation === 'landscape') { - if (storagePlacement !== lastPlacementLandscape && lastPlacementLandscape === 'top') webcamDraggableDispatch({ type: 'setplacementToTop' }); - if (storagePlacement !== lastPlacementLandscape && lastPlacementLandscape === 'bottom') webcamDraggableDispatch({ type: 'setplacementToBottom' }); - } - if ((prevOrientation == null || prevOrientation === 'landscape') && orientation === 'portrait') { - if (storagePlacement !== lastPlacementPortrait && lastPlacementPortrait === 'left') webcamDraggableDispatch({ type: 'setplacementToLeft' }); - if (storagePlacement !== lastPlacementPortrait && lastPlacementPortrait === 'right') webcamDraggableDispatch({ type: 'setplacementToRight' }); - } - } + window.addEventListener('layoutSizesSets', this.handleLayoutSizesSets); } componentWillUnmount() { - window.removeEventListener('resize', this.debouncedOnResize); document.removeEventListener('fullscreenchange', this.onFullscreenChange); - dispatchResizeEvent(); + window.removeEventListener('layoutSizesSets', this.handleLayoutSizesSets); } onFullscreenChange() { @@ -109,97 +71,115 @@ class WebcamDraggable extends PureComponent { } onResizeStart() { + const { layoutContextDispatch } = this.props; + this.setState({ resizing: true }); + layoutContextDispatch( + { + type: 'setWebcamsAreaResizing', + value: true, + }, + ); } - onWindowResize() { - const { webcamDraggableState, webcamDraggableDispatch } = this.props; - const { mediaSize } = webcamDraggableState; - const { width: stateWidth, height: stateHeight } = mediaSize; - const { width, height } = this.getMediaBounds(); + onResizeHandle(resizableWidth, resizableHeight) { + const { webcamsAreaResizable } = this.state; + const { layoutContextState, layoutContextDispatch } = this.props; + const { webcamsPlacement, webcamsAreaSize } = layoutContextState; + + layoutContextDispatch( + { + type: 'setAutoArrangeLayout', + value: false, + }, + ); + + const newWebcamsAreaResizable = { + width: Math.trunc(webcamsAreaResizable.width) + resizableWidth, + height: Math.trunc(webcamsAreaResizable.height) + resizableHeight, + }; + + const newWidth = webcamsPlacement === 'top' || webcamsPlacement === 'bottom' ? webcamsAreaSize.width : newWebcamsAreaResizable.width; + const newHeight = webcamsPlacement === 'left' || webcamsPlacement === 'right' ? webcamsAreaSize.height : newWebcamsAreaResizable.height; - if (stateWidth !== width || stateHeight !== height) { - webcamDraggableDispatch( + layoutContextDispatch( + { + type: 'setTempWebcamsAreaSize', + value: { + width: newWidth, + height: newHeight, + }, + }, + ); + + window.dispatchEvent(new Event('webcamAreaResize')); + } + + onResizeStop(resizableWidth, resizableHeight) { + const { webcamsAreaResizable } = this.state; + const { layoutContextState, layoutContextDispatch } = this.props; + const { webcamsPlacement, webcamsAreaSize } = layoutContextState; + + layoutContextDispatch( + { + type: 'setWebcamsAreaResizing', + value: false, + }, + ); + + const newWebcamsAreaResizable = { + width: Math.trunc(webcamsAreaResizable.width) + resizableWidth, + height: Math.trunc(webcamsAreaResizable.height) + resizableHeight, + }; + + if (webcamsPlacement === 'top' || webcamsPlacement === 'bottom') { + layoutContextDispatch( { - type: 'setMediaSize', - value: { - width, - height, - }, + type: 'setWebcamsAreaUserSetsHeight', + value: newWebcamsAreaResizable.height, }, ); - setTimeout(() => window.dispatchEvent(new Event('resize')), 300); } - } - - onResize() { - this.setPlacementPercent(); - } - onResizeStop() { - const { webcamDraggableState, webcamDraggableDispatch } = this.props; - const { optimalGrid } = webcamDraggableState; - if (optimalGrid) { - webcamDraggableDispatch( + if (webcamsPlacement === 'right' || webcamsPlacement === 'left') { + layoutContextDispatch( { - type: 'setVideoListSize', - value: { - width: optimalGrid.width, - height: optimalGrid.height, - }, + type: 'setWebcamsAreaUserSetsWidth', + value: newWebcamsAreaResizable.width, }, ); } - this.setPlacementPercent(); - window.dispatchEvent(new Event('resize')); + + const newWidth = webcamsPlacement === 'top' || webcamsPlacement === 'bottom' + ? webcamsAreaSize.width + : newWebcamsAreaResizable.width; + const newHeight = webcamsPlacement === 'left' || webcamsPlacement === 'right' + ? webcamsAreaSize.height + : newWebcamsAreaResizable.height; + + layoutContextDispatch( + { + type: 'setWebcamsAreaSize', + value: { + width: newWidth, + height: newHeight, + }, + }, + ); + + this.setWebcamsAreaResizable(newWidth, newHeight); + setTimeout(() => this.setState({ resizing: false }), 500); + window.dispatchEvent(new Event('webcamAreaResize')); } - setPlacementPercent() { - const { webcamDraggableState } = this.props; - const { optimalGrid, placement } = webcamDraggableState; - if (placement === 'top' || placement === 'bottom') { - const mediaSelection = document.querySelector('section[class^=media]'); - const mediaHeight = mediaSelection ? mediaSelection.offsetHeight : 0; - this.setState({ placementPercent: (optimalGrid.height * 100) / mediaHeight }); - } - if (placement === 'left' || placement === 'right') { - const mediaSelection = document.querySelector('section[class^=media]'); - const mediaWidth = mediaSelection ? mediaSelection.offsetWidth : 0; - this.setState({ placementPercent: (optimalGrid.width * 100) / mediaWidth }); - } + setWebcamsAreaResizable(width, height) { + this.setState({ + webcamsAreaResizable: { width, height }, + }); } - getMediaBounds() { - const { refMediaContainer, webcamDraggableState, webcamDraggableDispatch } = this.props; - const { mediaSize: mediaState } = webcamDraggableState; - const { current: mediaContainer } = refMediaContainer; - if (mediaContainer) { - const mediaContainerRect = mediaContainer.getBoundingClientRect(); - const { - top, left, width: newWidth, height: newHeight, - } = mediaContainerRect; - if ((mediaState.width === 0 || mediaState.height === 0) && (newWidth > 0 && newHeight > 0)) { - webcamDraggableDispatch( - { - type: 'setMediaSize', - value: { - newWidth, - newHeight, - }, - }, - ); - } - - return { - top, - left, - width: newWidth, - height: newHeight, - }; - } - return false; - } + setHideWebcams(hideWebcams) { this.setState({ hideWebcams }); } getWebcamsListBounds() { const { webcamDraggableState } = this.props; @@ -210,22 +190,20 @@ class WebcamDraggable extends PureComponent { top, left, width, height, } = videoListRefRect; return { - top, // 10 = margin - left, // 10 = margin - width, // 20 = margin - height, // 20 = margin + top, + left, + width, + height, }; } return false; } - recalculateAreaSize() { - this.onResizeStart(); - this.onResizeStop(); - } - calculatePosition() { - const { top: mediaTop, left: mediaLeft } = this.getMediaBounds(); + const { layoutContextState } = this.props; + const { mediaBounds } = layoutContextState; + + const { top: mediaTop, left: mediaLeft } = mediaBounds; const { top: webcamsListTop, left: webcamsListLeft } = this.getWebcamsListBounds(); const x = webcamsListLeft - mediaLeft; const y = webcamsListTop - mediaTop; @@ -235,6 +213,14 @@ class WebcamDraggable extends PureComponent { }; } + handleLayoutSizesSets() { + const { layoutContextState } = this.props; + const { webcamsAreaSize } = layoutContextState; + + this.setWebcamsAreaResizable(webcamsAreaSize.width, webcamsAreaSize.height); + this.setHideWebcams(false); + } + handleWebcamDragStart() { const { webcamDraggableDispatch } = this.props; const { x, y } = this.calculatePosition(); @@ -251,52 +237,69 @@ class WebcamDraggable extends PureComponent { } handleWebcamDragStop(e) { - const { webcamDraggableDispatch } = this.props; + const { webcamDraggableDispatch, layoutContextDispatch } = this.props; const targetClassname = JSON.stringify(e.target.className); + this.setHideWebcams(true); + + layoutContextDispatch( + { + type: 'setAutoArrangeLayout', + value: false, + }, + ); + if (targetClassname) { if (targetClassname.includes('Top')) { - webcamDraggableDispatch({ type: 'setplacementToTop' }); - webcamDraggableDispatch({ type: 'setLastPlacementLandscapeToTop' }); + layoutContextDispatch({ + type: 'setWebcamsPlacement', + value: 'top', + }); } else if (targetClassname.includes('Right')) { - webcamDraggableDispatch({ type: 'setplacementToRight' }); - webcamDraggableDispatch({ type: 'setLastPlacementPortraitToRight' }); + layoutContextDispatch({ + type: 'setWebcamsPlacement', + value: 'right', + }); } else if (targetClassname.includes('Bottom')) { - webcamDraggableDispatch({ type: 'setplacementToBottom' }); - webcamDraggableDispatch({ type: 'setLastPlacementLandscapeToBottom' }); + layoutContextDispatch({ + type: 'setWebcamsPlacement', + value: 'bottom', + }); } else if (targetClassname.includes('Left')) { - webcamDraggableDispatch({ type: 'setplacementToLeft' }); - webcamDraggableDispatch({ type: 'setLastPlacementPortraitToLeft' }); + layoutContextDispatch({ + type: 'setWebcamsPlacement', + value: 'left', + }); } } webcamDraggableDispatch({ type: 'dragEnd' }); - window.dispatchEvent(new Event('resize')); - setTimeout(this.recalculateAreaSize, 500); + window.dispatchEvent(new Event('webcamPlacementChange')); } render() { const { + layoutContextState, webcamDraggableState, swapLayout, hideOverlay, disableVideo, audioModalIsOpen, - refMediaContainer, } = this.props; + const { resizing, webcamsAreaResizable, hideWebcams } = this.state; + const { - resizing, - placementPercent, - } = this.state; + mediaBounds, + webcamsAreaSize, + } = layoutContextState; const { dragging, isCameraFullscreen, - videoListSize, optimalGrid, } = webcamDraggableState; - const placement = Storage.getItem('webcamPlacement'); + const webcamsPlacement = Storage.getItem('webcamsPlacement'); const lastPosition = Storage.getItem('webcamLastPosition') || { x: 0, y: 0 }; @@ -323,7 +326,7 @@ class WebcamDraggable extends PureComponent { const { width: mediaWidth, height: mediaHeight, - } = this.getMediaBounds(); + } = mediaBounds; const { width: webcamsWidth, @@ -346,57 +349,16 @@ class WebcamDraggable extends PureComponent { [styles.fullHeight]: swapLayout, }); - const { current: mediaContainer } = refMediaContainer; - let layout = 'vertical'; - if (mediaContainer) { - const classNameMediaContainer = mediaContainer.className; - if (classNameMediaContainer.includes('containerH')) { - layout = 'horizontal'; - } else { - layout = 'vertical'; - } - } - const overlayClassName = cx({ [styles.overlay]: true, [styles.hideOverlay]: hideOverlay, [styles.floatingOverlay]: dragging, [styles.autoWidth]: dragging, - [styles.fullWidth]: ( - ( - placement === 'top' - || placement === 'bottom' - ) - || swapLayout - ) - && !dragging, - [styles.fullHeight]: ( - ( - placement === 'left' - && placement === 'right' - ) - || swapLayout - ) - && !dragging, - [styles.overlayToTop]: placement === 'top' && !dragging, - [styles.overlayToRight]: placement === 'right' && !dragging, - [styles.overlayToBottom]: placement === 'bottom' && !dragging, - [styles.overlayToLeft]: placement === 'left' && !dragging, + [styles.overlayToTop]: webcamsPlacement === 'top' && !dragging, + [styles.overlayToRight]: webcamsPlacement === 'right' && !dragging, + [styles.overlayToBottom]: webcamsPlacement === 'bottom' && !dragging, + [styles.overlayToLeft]: webcamsPlacement === 'left' && !dragging, [styles.dragging]: dragging, - [styles.hide]: ( - ( - placement === 'left' - || placement === 'right' - ) - && layout === 'vertical' - ) - || ( - ( - placement === 'top' - || placement === 'bottom' - ) - && layout === 'horizontal' - ), }); const dropZoneTopClassName = cx({ @@ -443,33 +405,17 @@ class WebcamDraggable extends PureComponent { [styles.dropZoneBgRight]: true, }); - const mediaSelection = document.querySelector('section[class^=media]'); - const mHeight = mediaSelection ? mediaSelection.offsetHeight : 0; - const mWidth = mediaSelection ? mediaSelection.offsetWidth : 0; - - let resizeWidth; - let resizeHeight; - if (resizing && (placement === 'top' || placement === 'bottom') && !dragging) { - resizeWidth = '100%'; - resizeHeight = videoListSize.height; - } - if (!resizing && (placement === 'top' || placement === 'bottom') && !dragging) { - resizeWidth = '100%'; - resizeHeight = mHeight * (placementPercent / 100); - } - - if (resizing && (placement === 'left' || placement === 'right') && !dragging) { - resizeWidth = videoListSize.width; - resizeHeight = '100%'; - } - if (!resizing && (placement === 'left' || placement === 'right') && !dragging) { - resizeWidth = mWidth * (placementPercent / 100); - resizeHeight = '100%'; - } - + let sizeHeight; + let sizeWidth; if (dragging) { - resizeHeight = optimalGrid.height; - resizeWidth = optimalGrid.width; + sizeWidth = optimalGrid.width; + sizeHeight = optimalGrid.height; + } else if (resizing) { + sizeWidth = webcamsAreaResizable.width; + sizeHeight = webcamsAreaResizable.height; + } else { + sizeWidth = webcamsAreaSize.width; + sizeHeight = webcamsAreaSize.height; } return ( @@ -499,26 +445,37 @@ class WebcamDraggable extends PureComponent { onStart={this.handleWebcamDragStart} onStop={this.handleWebcamDragStop} onMouseDown={e => e.preventDefault()} - disabled={swapLayout || isCameraFullscreen || BROWSER_ISMOBILE} + disabled={swapLayout || isCameraFullscreen || BROWSER_ISMOBILE || resizing} position={position} > <Resizable + minWidth={mediaWidth * WEBCAMSAREA_MIN_PERCENT} + minHeight={mediaHeight * WEBCAMSAREA_MIN_PERCENT} + maxWidth={ + webcamsPlacement === 'left' || webcamsPlacement === 'right' + ? mediaWidth - PRESENTATIONAREA_MIN_WIDTH + : undefined + } size={ { - height: resizeHeight, - width: resizeWidth, + width: sizeWidth, + height: sizeHeight, } } - lockAspectRatio + // lockAspectRatio handleWrapperClass="resizeWrapper" onResizeStart={this.onResizeStart} - onResize={dispatchResizeEvent} - onResizeStop={this.onResizeStop} + onResize={(e, direction, ref, d) => { + this.onResizeHandle(d.width, d.height); + }} + onResizeStop={(e, direction, ref, d) => { + this.onResizeStop(d.width, d.height); + }} enable={{ - top: (placement === 'bottom') && !swapLayout, - bottom: (placement === 'top') && !swapLayout, - left: (placement === 'right') && !swapLayout, - right: (placement === 'left') && !swapLayout, + top: (webcamsPlacement === 'bottom') && !swapLayout, + bottom: (webcamsPlacement === 'top') && !swapLayout, + left: (webcamsPlacement === 'right') && !swapLayout, + right: (webcamsPlacement === 'left') && !swapLayout, topLeft: false, topRight: false, bottomLeft: false, @@ -527,10 +484,12 @@ class WebcamDraggable extends PureComponent { className={ !swapLayout ? overlayClassName - : contentClassName} + : contentClassName + } style={{ marginLeft: 0, marginRight: 0, + display: hideWebcams ? 'none' : undefined, }} > { @@ -572,4 +531,4 @@ class WebcamDraggable extends PureComponent { WebcamDraggable.propTypes = propTypes; WebcamDraggable.defaultProps = defaultProps; -export default withDraggableConsumer(WebcamDraggable); +export default withDraggableConsumer(withLayoutConsumer(WebcamDraggable)); diff --git a/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/context.jsx b/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/context.jsx index a0981de896110c2c1fc8fc6a6ac613bf2a7c6a73..f86b406f7701f5250ebf8f463fb482c1bbdda712 100644 --- a/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/context.jsx +++ b/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/context.jsx @@ -1,19 +1,9 @@ import React, { createContext, useReducer, useEffect } from 'react'; -import Storage from '../../../services/storage/session'; - -const { webcamsDefaultPlacement } = Meteor.settings.public.layout; +import Storage from '/imports/ui/services/storage/session'; export const WebcamDraggableContext = createContext(); const initialState = { - placement: webcamsDefaultPlacement || 'top', - lastPlacementLandscape: 'top', - lastPlacementPortrait: 'left', - orientation: null, - mediaSize: { - width: 0, - height: 0, - }, videoListSize: { width: 0, height: 0, @@ -39,81 +29,6 @@ const initialState = { const reducer = (state, action) => { switch (action.type) { - case 'setplacementToTop': { - return { - ...state, - placement: 'top', - }; - } - case 'setplacementToRight': { - return { - ...state, - placement: 'right', - }; - } - case 'setplacementToBottom': { - return { - ...state, - placement: 'bottom', - }; - } - case 'setplacementToLeft': { - return { - ...state, - placement: 'left', - }; - } - case 'setLastPlacementPortraitToLeft': { - return { - ...state, - lastPlacementPortrait: 'left', - }; - } - case 'setLastPlacementPortraitToRight': { - return { - ...state, - lastPlacementPortrait: 'right', - }; - } - case 'setLastPlacementLandscapeToTop': { - return { - ...state, - lastPlacementLandscape: 'top', - }; - } - case 'setLastPlacementLandscapeToBottom': { - return { - ...state, - lastPlacementLandscape: 'bottom', - }; - } - case 'setplacementToFloating': { - return { - ...state, - placement: 'floating', - }; - } - case 'setOrientationToLandscape': { - return { - ...state, - orientation: 'landscape', - }; - } - case 'setOrientationToPortrait': { - return { - ...state, - orientation: 'portrait', - }; - } - case 'setMediaSize': { - return { - ...state, - mediaSize: { - width: action.value.width, - height: action.value.height, - }, - }; - } case 'setVideoListSize': { return { ...state, @@ -147,15 +62,6 @@ const reducer = (state, action) => { }, }; } - case 'setLastPosition': { - return { - ...state, - lastPosition: { - x: action.value.x, - y: action.value.y, - }, - }; - } case 'setVideoRef': { return { ...state, @@ -214,7 +120,6 @@ const ContextProvider = (props) => { } = webcamDraggableState; const { children } = props; useEffect(() => { - Storage.setItem('webcamPlacement', placement); Storage.setItem('webcamLastPlacementLandscape', lastPlacementLandscape); Storage.setItem('webcamlastPlacementPortrait', lastPlacementPortrait); Storage.setItem('webcamLastPosition', lastPosition); diff --git a/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx b/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx index 73839c248b65d28273a8ea1f7ea0ddcc3c9be7bd..d314d0101e5a0d7eb4436f7c9f1a95e9d3e24717 100755 --- a/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx @@ -11,6 +11,7 @@ import { styles } from './styles'; import logger from '/imports/startup/client/logger'; import Users from '/imports/api/users'; import AudioManager from '/imports/ui/services/audio-manager'; +import { meetingIsBreakout } from '/imports/ui/components/app/service'; const intlMessage = defineMessages({ 410: { @@ -88,6 +89,7 @@ const propTypes = { formatMessage: PropTypes.func.isRequired, }).isRequired, code: PropTypes.string.isRequired, + reason: PropTypes.string.isRequired, }; class MeetingEnded extends PureComponent { @@ -128,6 +130,7 @@ class MeetingEnded extends PureComponent { } = this.state; if (selected <= 0) { + if (meetingIsBreakout()) window.close(); logoutRouteHandler(); return; } @@ -168,14 +171,12 @@ class MeetingEnded extends PureComponent { } render() { - const { intl, code } = this.props; - const { - selected, - } = this.state; + const { code, intl, reason } = this.props; + const { selected } = this.state; const noRating = selected <= 0; - logger.info({ logCode: 'meeting_ended_code', extraInfo: { endedCode: code } }, 'Meeting ended component'); + logger.info({ logCode: 'meeting_ended_code', extraInfo: { endedCode: code, reason } }, 'Meeting ended component'); return ( <div className={styles.parent}> @@ -183,7 +184,7 @@ class MeetingEnded extends PureComponent { <div className={styles.content}> <h1 className={styles.title}> { - intl.formatMessage(intlMessage[code] || intlMessage[430]) + intl.formatMessage(intlMessage[reason] || intlMessage[430]) } </h1> <div className={styles.text}> diff --git a/bigbluebutton-html5/imports/ui/components/meeting-ended/rating/component.jsx b/bigbluebutton-html5/imports/ui/components/meeting-ended/rating/component.jsx index 7e2e205c11b3f7ff5a32c73bb6df798bff58d9ed..af4556b9e3df32cb1713f357ceb74397721f47ef 100755 --- a/bigbluebutton-html5/imports/ui/components/meeting-ended/rating/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/meeting-ended/rating/component.jsx @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import _ from 'lodash'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import { styles } from './styles'; const intlMessages = defineMessages({ @@ -16,7 +16,7 @@ const intlMessages = defineMessages({ }); const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, onRate: PropTypes.func.isRequired, total: PropTypes.string.isRequired, }; diff --git a/bigbluebutton-html5/imports/ui/components/meeting-ended/rating/styles.scss b/bigbluebutton-html5/imports/ui/components/meeting-ended/rating/styles.scss index c972fafa90394076b400049685232cd644923475..9d76a3763c0f8bc051c0711cf1fdfd47da667484 100755 --- a/bigbluebutton-html5/imports/ui/components/meeting-ended/rating/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/meeting-ended/rating/styles.scss @@ -1,4 +1,4 @@ -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/breakpoints"; .starRating { font-family: 'bbb-icons' !important; diff --git a/bigbluebutton-html5/imports/ui/components/meeting-ended/styles.scss b/bigbluebutton-html5/imports/ui/components/meeting-ended/styles.scss index c61656bc28c41e483de6ef25a005893efb669ee8..0e10446cd0cef78d9ee290c1e6b2c9f4c18b5eae 100755 --- a/bigbluebutton-html5/imports/ui/components/meeting-ended/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/meeting-ended/styles.scss @@ -1,4 +1,4 @@ -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/breakpoints"; .parent { height: 100%; diff --git a/bigbluebutton-html5/imports/ui/components/modal/base/styles.scss b/bigbluebutton-html5/imports/ui/components/modal/base/styles.scss index 171d92a161d7dcaa384cb4b6ebc3f6ea6d2ffeb5..7ad865a129319bc647c6cbf4ddf6cc9377b29815 100644 --- a/bigbluebutton-html5/imports/ui/components/modal/base/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/modal/base/styles.scss @@ -1,4 +1,4 @@ -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/breakpoints"; @import "/imports/ui/stylesheets/mixins/_scrollable"; .modal { diff --git a/bigbluebutton-html5/imports/ui/components/modal/fullscreen/styles.scss b/bigbluebutton-html5/imports/ui/components/modal/fullscreen/styles.scss index 08a6fdb559dd5b4715662bb9044b772b94ca22fb..511d7a68fd51c87ebb0e449e3d4672b95f2f4237 100644 --- a/bigbluebutton-html5/imports/ui/components/modal/fullscreen/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/modal/fullscreen/styles.scss @@ -1,4 +1,5 @@ -@import "../../../stylesheets/variables/_all"; +@import "../../../stylesheets/variables/breakpoints"; +@import "/imports/ui/stylesheets/variables/placeholders"; @import "/imports/ui/stylesheets/mixins/_indicators"; :root { @@ -7,7 +8,7 @@ } .modal { - @include highContrastOutline(); + @extend %highContrastOutline; outline-style: solid; display: flex; flex-direction: column; diff --git a/bigbluebutton-html5/imports/ui/components/modal/remove-user/styles.scss b/bigbluebutton-html5/imports/ui/components/modal/remove-user/styles.scss index ac7ab070c28a24fe37d318c8f30348927fbf5610..a915b9f65f496b01be97d72a675c0b10b02ddd1f 100644 --- a/bigbluebutton-html5/imports/ui/components/modal/remove-user/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/modal/remove-user/styles.scss @@ -1,7 +1,3 @@ -@import "/imports/ui/components/user-list/styles.scss"; -@import "/imports/ui/stylesheets/variables/_all"; -@import '/imports/ui/stylesheets/mixins/_indicators'; -@import '/imports/ui/stylesheets/mixins/focus'; @import "/imports/ui/components/modal/simple/styles"; :root { diff --git a/bigbluebutton-html5/imports/ui/components/modal/simple/styles.scss b/bigbluebutton-html5/imports/ui/components/modal/simple/styles.scss index fd5f4ea750ca94b2fc97420e8e73efa4d73584a1..8c99b2cf57c16e5b8b1ae7c2161f13af76a27c17 100644 --- a/bigbluebutton-html5/imports/ui/components/modal/simple/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/modal/simple/styles.scss @@ -1,9 +1,9 @@ -@import "/imports/ui/stylesheets/variables/_all"; @import "/imports/ui/stylesheets/mixins/_indicators"; +@import "/imports/ui/stylesheets/variables/placeholders"; @import "../base/styles"; .modal { - @include highContrastOutline(); + @extend %highContrastOutline; outline-style: solid; display: flex; flex-direction: column; diff --git a/bigbluebutton-html5/imports/ui/components/muted-alert/styles.scss b/bigbluebutton-html5/imports/ui/components/muted-alert/styles.scss index f1076a2cb4204fea2603579e717a6f5d5a7c0f32..73e0f45e8caa11a00a585d70394867bef9bfb048 100644 --- a/bigbluebutton-html5/imports/ui/components/muted-alert/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/muted-alert/styles.scss @@ -1,4 +1,4 @@ -@import "../../stylesheets/variables/_all"; +@import "../../stylesheets/variables/breakpoints"; .muteWarning { position: absolute; diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx index 0a23e1e2c2bd5aadb3661c68b5f9ae7ee66fabbd..5acf94dcb45050cb205054522f92e77d0cd8d66e 100755 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx @@ -1,4 +1,4 @@ -import React, { PureComponent } from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { Session } from 'meteor/session'; import cx from 'classnames'; @@ -8,12 +8,11 @@ import getFromUserSettings from '/imports/ui/services/users-settings'; import { defineMessages, injectIntl } from 'react-intl'; import Icon from '../icon/component'; import { styles } from './styles.scss'; -import Button from '../button/component'; +import Button from '/imports/ui/components/button/component'; import RecordingIndicator from './recording-indicator/container'; import TalkingIndicatorContainer from '/imports/ui/components/nav-bar/talking-indicator/container'; import SettingsDropdownContainer from './settings-dropdown/container'; - const intlMessages = defineMessages({ toggleUserListLabel: { id: 'app.navBar.userListToggleBtnLabel', @@ -41,7 +40,7 @@ const defaultProps = { shortcuts: '', }; -class NavBar extends PureComponent { +class NavBar extends Component { static handleToggleUserList() { Session.set( 'openPanel', @@ -50,6 +49,8 @@ class NavBar extends PureComponent { : 'userlist', ); Session.set('idChatOpen', ''); + + window.dispatchEvent(new Event('panelChanged')); } componentDidMount() { @@ -89,7 +90,9 @@ class NavBar extends PureComponent { ariaLabel += hasUnreadMessages ? (` ${intl.formatMessage(intlMessages.newMessages)}`) : ''; return ( - <div className={styles.navbar}> + <div + className={styles.navbar} + > <div className={styles.top}> <div className={styles.left}> {!isExpanded ? null diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/recording-indicator/component.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/recording-indicator/component.jsx index 586d32a192230d7de676a0aa996d5a4af41cae95..c5ed823bb2558d9ddc1321b8a3af7c2986ea3c5e 100755 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/recording-indicator/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/recording-indicator/component.jsx @@ -3,7 +3,7 @@ import RecordingContainer from '/imports/ui/components/recording/container'; import humanizeSeconds from '/imports/utils/humanizeSeconds'; import Tooltip from '/imports/ui/components/tooltip/component'; import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import { styles } from './styles'; const intlMessages = defineMessages({ @@ -46,7 +46,7 @@ const intlMessages = defineMessages({ }); const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, amIModerator: PropTypes.bool, record: PropTypes.bool, recording: PropTypes.bool, diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/recording-indicator/styles.scss b/bigbluebutton-html5/imports/ui/components/nav-bar/recording-indicator/styles.scss index 80f412c6044436a59c7060a369cd2b90380f2b5d..50d5d2bfc913fd8cef984c915dc174278dbf33c9 100755 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/recording-indicator/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/recording-indicator/styles.scss @@ -1,6 +1,5 @@ -@import "../../../stylesheets/variables/_all"; @import '/imports/ui/stylesheets/mixins/_indicators'; -@import "../../../stylesheets/variables/typography"; +@import "/imports/ui/stylesheets/variables/placeholders"; .visuallyHidden { position: absolute; @@ -99,13 +98,13 @@ .recordingIndicator { &:hover { - @include highContrastOutline(); + @extend %highContrastOutline; } &:active, &:focus, &:focus-within { - @include highContrastOutline(); + @extend %highContrastOutline; outline-style: solid; } } diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx index 7de199723138be1e1fb5cd1ca6f91c0c5b5d48b6..39c11f4db1ac9c0439e953c93403e7246284980d 100755 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx @@ -1,5 +1,5 @@ import React, { PureComponent } from 'react'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import _ from 'lodash'; import PropTypes from 'prop-types'; import { withModalMounter } from '/imports/ui/components/modal/service'; @@ -92,7 +92,7 @@ const intlMessages = defineMessages({ }); const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, handleToggleFullscreen: PropTypes.func.isRequired, mountModal: PropTypes.func.isRequired, noIOSFullscreen: PropTypes.bool, diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/styles.scss b/bigbluebutton-html5/imports/ui/components/nav-bar/styles.scss index b3e4205a4709f36587341f26c2b1991f16968a83..c3117922b61782319a127ef000f8e9c498ec4bb7 100755 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/styles.scss @@ -1,4 +1,4 @@ -@import "../../stylesheets/variables/_all"; +@import "../../stylesheets/variables/breakpoints"; :root { --mobile-nav-height: 5rem; @@ -11,7 +11,6 @@ .navbar { display: flex; flex-direction: column; - height:var(--mobile-nav-height); } .top, diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/talking-indicator/styles.scss b/bigbluebutton-html5/imports/ui/components/nav-bar/talking-indicator/styles.scss index f4779544e2f12b9630061420713454759e7c2279..6699160e144db840b9c0bd137a6df4ba99d3fc83 100644 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/talking-indicator/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/talking-indicator/styles.scss @@ -1,6 +1,6 @@ -@import "../../../stylesheets/variables/_all"; -@import "../../../stylesheets/variables/typography"; -@import "../../../stylesheets/mixins/_indicators"; +@import "/imports/ui/stylesheets/variables/breakpoints"; +@import "/imports/ui/stylesheets/mixins/_indicators"; +@import "/imports/ui/stylesheets/variables/placeholders"; :root { --spoke-opacity: .5; @@ -45,9 +45,9 @@ } .talker { + @extend %highContrastOutline; flex: 0 0 auto; - @include highContrastOutline(); - color: white; + color: var(--color-white); font-weight: var(--talker-font-weight); border-radius: var(--talker-border-radius) var(--talker-border-radius); font-size: var(--font-size-base); diff --git a/bigbluebutton-html5/imports/ui/components/note/styles.scss b/bigbluebutton-html5/imports/ui/components/note/styles.scss index 7186bf114f0863b3167f03604a345af67c5b2bf3..bef07c5c5ffde56cc430a567829da57a6a709e1e 100644 --- a/bigbluebutton-html5/imports/ui/components/note/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/note/styles.scss @@ -1,39 +1,38 @@ @import "/imports/ui/stylesheets/mixins/focus"; -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/placeholders"; +@import "/imports/ui/stylesheets/variables/breakpoints"; .note { background-color: var(--color-white); - padding: var(--md-padding-x); + padding: + var(--md-padding-x) + var(--md-padding-y) + var(--md-padding-x) + var(--md-padding-x); + display: flex; flex-grow: 1; flex-direction: column; justify-content: space-around; overflow: hidden; height: 100vh; - transform: translateZ(0); + + :global(.browser-chrome) & { + transform: translateZ(0); + } + + @include mq($small-only) { + transform: none !important; + } } .header { + position: relative; + top: var(--poll-header-offset); display: flex; flex-direction: row; - align-items: left; - flex-shrink: 0; - - a { - @include elementFocus(var(--color-primary)); - padding: 0 0 var(--sm-padding-y) var(--sm-padding-y); - text-decoration: none; - display: block; - - [dir="rtl"] & { - padding: 0 var(--sm-padding-y) var(--sm-padding-y) 0; - } - } - - [class^="icon-bbb-"], - [class*=" icon-bbb-"] { - font-size: 85%; - } + align-items: center; + justify-content: space-between; } .title { @@ -41,9 +40,7 @@ flex: 1; & > button, button:hover { - margin-top: 0; - padding-top: 0; - border-top: 0; + max-width: var(--toast-content-width); } } @@ -51,16 +48,27 @@ position: relative; background-color: var(--color-white); display: block; - margin: 4px; - margin-bottom: 2px; - padding: inherit inherit inherit 0; + margin: var(--border-size-large); + margin-bottom: var(--border-size); + padding-left: 0; + padding-right: inherit; [dir="rtl"] & { - padding: inherit 0 inherit inherit; + padding-left: inherit; + padding-right: 0; } > i { - color: black; + color: var(--color-gray-dark); + font-size: smaller; + + [dir="rtl"] & { + -webkit-transform: scale(-1, 1); + -moz-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + -o-transform: scale(-1, 1); + transform: scale(-1, 1); + } } &:hover { diff --git a/bigbluebutton-html5/imports/ui/components/notifications-bar/styles.scss b/bigbluebutton-html5/imports/ui/components/notifications-bar/styles.scss index d893fa114a9d131395755be960ddeb64d62513d5..5d7186d7a6cc388471f20c9f8530a3bd5fdcde3e 100644 --- a/bigbluebutton-html5/imports/ui/components/notifications-bar/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/notifications-bar/styles.scss @@ -1,5 +1,3 @@ -@import "../../stylesheets/variables/_all"; - :root { --nb-default-color: var(--color-gray); --nb-default-bg: var(--color-white); diff --git a/bigbluebutton-html5/imports/ui/components/panel-manager/component.jsx b/bigbluebutton-html5/imports/ui/components/panel-manager/component.jsx index 1c867fa59c7648afbbed209ac61ee123c22c53e6..6fee72c51deabdf83e61bf747ff71f0980e1aa4f 100755 --- a/bigbluebutton-html5/imports/ui/components/panel-manager/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/panel-manager/component.jsx @@ -1,4 +1,4 @@ -import React, { PureComponent } from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import BreakoutRoomContainer from '/imports/ui/components/breakout-room/container'; import UserListContainer from '/imports/ui/components/user-list/container'; @@ -11,6 +11,13 @@ import { defineMessages, injectIntl } from 'react-intl'; import Resizable from 're-resizable'; import { styles } from '/imports/ui/components/app/styles'; import _ from 'lodash'; +import { withLayoutConsumer } from '/imports/ui/components/layout/context'; +import { + USERLIST_MIN_WIDTH, + USERLIST_MAX_WIDTH, + CHAT_MIN_WIDTH, + CHAT_MAX_WIDTH, +} from '/imports/ui/components/layout/layout-manager'; const intlMessages = defineMessages({ chatLabel: { @@ -43,12 +50,8 @@ const propTypes = { const DEFAULT_PANEL_WIDTH = 340; // Variables for resizing user-list. -const USERLIST_MIN_WIDTH_PX = 150; -const USERLIST_MAX_WIDTH_PX = 240; - -// Variables for resizing chat. -const CHAT_MIN_WIDTH = 150; -const CHAT_MAX_WIDTH = 350; +const USERLIST_MIN_WIDTH_PX = USERLIST_MIN_WIDTH; +const USERLIST_MAX_WIDTH_PX = USERLIST_MAX_WIDTH; // Variables for resizing poll. const POLL_MIN_WIDTH = 320; @@ -66,11 +69,9 @@ const CAPTIONS_MAX_WIDTH = 400; const WAITING_MIN_WIDTH = DEFAULT_PANEL_WIDTH; const WAITING_MAX_WIDTH = 800; -const dispatchResizeEvent = () => window.dispatchEvent(new Event('resize')); - -class PanelManager extends PureComponent { - constructor() { - super(); +class PanelManager extends Component { + constructor(props) { + super(props); this.padKey = _.uniqueId('resize-pad-'); this.userlistKey = _.uniqueId('userlist-'); @@ -81,23 +82,211 @@ class PanelManager extends PureComponent { this.captionsKey = _.uniqueId('captions-'); this.waitingUsers = _.uniqueId('waitingUsers-'); + const { layoutContextState } = props; + const { userListSize, chatSize } = layoutContextState; + this.state = { - chatWidth: DEFAULT_PANEL_WIDTH, - pollWidth: DEFAULT_PANEL_WIDTH, - userlistWidth: 180, + userlistWidth: userListSize.width, + chatWidth: chatSize.width, noteWidth: DEFAULT_PANEL_WIDTH, captionsWidth: DEFAULT_PANEL_WIDTH, + pollWidth: DEFAULT_PANEL_WIDTH, waitingWidth: DEFAULT_PANEL_WIDTH, + breakoutRoomWidth: 0, }; + + this.setUserListWidth = this.setUserListWidth.bind(this); } componentDidUpdate(prevProps) { - const { openPanel } = this.props; - const { openPanel: oldOpenPanel } = prevProps; - - if (openPanel !== oldOpenPanel) { - window.dispatchEvent(new Event('resize')); + const { + userlistWidth, + chatWidth, + noteWidth, + captionsWidth, + pollWidth, + waitingWidth, + breakoutRoomWidth, + } = this.state; + const { layoutContextState } = this.props; + const { + userListSize, + chatSize, + noteSize, + captionsSize, + pollSize, + waitingSize, + breakoutRoomSize, + } = layoutContextState; + const { layoutContextState: oldLayoutContextState } = prevProps; + const { + userListSize: oldUserListSize, + chatSize: oldChatSize, + noteSize: oldNoteSize, + captionsSize: oldCaptionsSize, + pollSize: oldPollSize, + waitingSize: oldWaitingSize, + breakoutRoomSize: oldBreakoutRoomSize, + } = oldLayoutContextState; + + if (userListSize.width !== oldUserListSize.width && userListSize.width !== userlistWidth) { + this.setUserListWidth(userListSize.width); } + if (chatSize.width !== oldChatSize.width && chatSize.width !== chatWidth) { + this.setChatWidth(chatSize.width); + } + if (noteSize.width !== oldNoteSize.width && noteSize.width !== noteWidth) { + this.setNoteWidth(noteSize.width); + } + if (captionsSize.width !== oldCaptionsSize.width && captionsSize.width !== captionsWidth) { + this.setCaptionsWidth(captionsSize.width); + } + if (pollSize.width !== oldPollSize.width && pollSize.width !== pollWidth) { + this.setPollWidth(pollSize.width); + } + if (waitingSize.width !== oldWaitingSize.width && waitingSize.width !== waitingWidth) { + this.setWaitingWidth(waitingSize.width); + } + if (breakoutRoomSize.width !== oldBreakoutRoomSize.width + && breakoutRoomSize.width !== breakoutRoomWidth) { + this.setBreakoutRoomWidth(breakoutRoomSize.width); + } + } + + setUserListWidth(userlistWidth) { + this.setState({ userlistWidth }); + } + + setChatWidth(chatWidth) { + this.setState({ chatWidth }); + } + + setNoteWidth(noteWidth) { + this.setState({ noteWidth }); + } + + setCaptionsWidth(captionsWidth) { + this.setState({ captionsWidth }); + } + + setPollWidth(pollWidth) { + this.setState({ pollWidth }); + } + + setWaitingWidth(waitingWidth) { + this.setState({ waitingWidth }); + } + + setBreakoutRoomWidth(breakoutRoomWidth) { + this.setState({ breakoutRoomWidth }); + } + + userListResizeStop(addvalue) { + const { userlistWidth } = this.state; + const { layoutContextDispatch } = this.props; + + this.setUserListWidth(userlistWidth + addvalue); + + layoutContextDispatch( + { + type: 'setUserListSize', + value: { + width: userlistWidth + addvalue, + }, + }, + ); + + window.dispatchEvent(new Event('panelChanged')); + } + + chatResizeStop(addvalue) { + const { chatWidth } = this.state; + const { layoutContextDispatch } = this.props; + + this.setChatWidth(chatWidth + addvalue); + + layoutContextDispatch( + { + type: 'setChatSize', + value: { + width: chatWidth + addvalue, + }, + }, + ); + + window.dispatchEvent(new Event('panelChanged')); + } + + noteResizeStop(addvalue) { + const { noteWidth } = this.state; + const { layoutContextDispatch } = this.props; + + this.setNoteWidth(noteWidth + addvalue); + + layoutContextDispatch( + { + type: 'setNoteSize', + value: { + width: noteWidth + addvalue, + }, + }, + ); + + window.dispatchEvent(new Event('panelChanged')); + } + + captionsResizeStop(addvalue) { + const { captionsWidth } = this.state; + const { layoutContextDispatch } = this.props; + + this.setCaptionsWidth(captionsWidth + addvalue); + + layoutContextDispatch( + { + type: 'setCaptionsSize', + value: { + width: captionsWidth + addvalue, + }, + }, + ); + + window.dispatchEvent(new Event('panelChanged')); + } + + pollResizeStop(addvalue) { + const { pollWidth } = this.state; + const { layoutContextDispatch } = this.props; + + this.setPollWidth(pollWidth + addvalue); + + layoutContextDispatch( + { + type: 'setPollSize', + value: { + width: pollWidth + addvalue, + }, + }, + ); + + window.dispatchEvent(new Event('panelChanged')); + } + + waitingResizeStop(addvalue) { + const { waitingWidth } = this.state; + const { layoutContextDispatch } = this.props; + + this.setWaitingWidth(waitingWidth + addvalue); + + layoutContextDispatch( + { + type: 'setWaitingUsersPanelSize', + value: { + width: waitingWidth + addvalue, + }, + }, + ); + + window.dispatchEvent(new Event('panelChanged')); } renderUserList() { @@ -145,11 +334,8 @@ class PanelManager extends PureComponent { enable={resizableEnableOptions} key={this.userlistKey} size={{ width: userlistWidth }} - onResize={dispatchResizeEvent} onResizeStop={(e, direction, ref, d) => { - this.setState({ - userlistWidth: userlistWidth + d.width, - }); + this.userListResizeStop(d.width); }} > {this.renderUserList()} @@ -194,11 +380,8 @@ class PanelManager extends PureComponent { enable={resizableEnableOptions} key={this.chatKey} size={{ width: chatWidth }} - onResize={dispatchResizeEvent} onResizeStop={(e, direction, ref, d) => { - this.setState({ - chatWidth: chatWidth + d.width, - }); + this.chatResizeStop(d.width); }} > {this.renderChat()} @@ -243,11 +426,8 @@ class PanelManager extends PureComponent { enable={resizableEnableOptions} key={this.noteKey} size={{ width: noteWidth }} - onResize={dispatchResizeEvent} onResizeStop={(e, direction, ref, d) => { - this.setState({ - noteWidth: noteWidth + d.width, - }); + this.noteResizeStop(d.width); }} > {this.renderNote()} @@ -292,11 +472,8 @@ class PanelManager extends PureComponent { enable={resizableEnableOptions} key={this.captionsKey} size={{ width: captionsWidth }} - onResize={dispatchResizeEvent} onResizeStop={(e, direction, ref, d) => { - this.setState({ - captionsWidth: captionsWidth + d.width, - }); + this.captionsResizeStop(captionsWidth + d.width); }} > {this.renderCaptions()} @@ -341,11 +518,8 @@ class PanelManager extends PureComponent { enable={resizableEnableOptions} key={this.waitingUsers} size={{ width: waitingWidth }} - onResize={dispatchResizeEvent} onResizeStop={(e, direction, ref, d) => { - this.setState({ - waitingWidth: waitingWidth + d.width, - }); + this.waitingResizeStop(waitingWidth + d.width); }} > {this.renderWaitingUsersPanel()} @@ -354,8 +528,15 @@ class PanelManager extends PureComponent { } renderBreakoutRoom() { + const { breakoutRoomWidth } = this.state; return ( - <div className={styles.breakoutRoom} key={this.breakoutroomKey}> + <div + className={styles.breakoutRoom} + key={this.breakoutroomKey} + style={{ + width: breakoutRoomWidth, + }} + > <BreakoutRoomContainer /> </div> ); @@ -393,10 +574,8 @@ class PanelManager extends PureComponent { key={this.pollKey} size={{ width: pollWidth }} onResizeStop={(e, direction, ref, d) => { - window.dispatchEvent(new Event('resize')); - this.setState({ - pollWidth: pollWidth + d.width, - }); + // window.dispatchEvent(new Event('resize')); + this.pollResizeStop(pollWidth + d.width); }} > {this.renderPoll()} @@ -408,6 +587,7 @@ class PanelManager extends PureComponent { const { enableResize, openPanel } = this.props; if (openPanel === '') return null; const panels = []; + if (enableResize) { panels.push( this.renderUserListResizable(), @@ -469,6 +649,6 @@ class PanelManager extends PureComponent { } } -export default injectIntl(PanelManager); +export default injectIntl(withLayoutConsumer(PanelManager)); PanelManager.propTypes = propTypes; diff --git a/bigbluebutton-html5/imports/ui/components/poll/component.jsx b/bigbluebutton-html5/imports/ui/components/poll/component.jsx index 3efbc9a188ab83d1948f34f4842a69258fedcfc2..e99af79f1733ae3cf857796d493e1d51f1c6794e 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/poll/component.jsx @@ -84,6 +84,7 @@ const intlMessages = defineMessages({ }, }); +const CHAT_ENABLED = Meteor.settings.public.chat.enabled; const MAX_CUSTOM_FIELDS = Meteor.settings.public.poll.max_custom; const MAX_INPUT_CHARS = 45; @@ -315,7 +316,7 @@ class Poll extends Component { currentSlide, } = this.props; - if (!currentSlide) return this.renderNoSlidePanel(); + if (!CHAT_ENABLED && !currentSlide) return this.renderNoSlidePanel(); if (isPolling || (!isPolling && currentPoll)) { return this.renderActivePollOptions(); diff --git a/bigbluebutton-html5/imports/ui/components/poll/container.jsx b/bigbluebutton-html5/imports/ui/components/poll/container.jsx index a0a2c52af8a3908ffa84e706e4a088ccbbf17490..ef3d4f003b84298dbaedec41d82b36e54a2a95c6 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/poll/container.jsx @@ -1,12 +1,14 @@ import React from 'react'; import { makeCall } from '/imports/ui/services/api'; import { withTracker } from 'meteor/react-meteor-data'; -import Auth from '/imports/ui/services/auth'; import Presentations from '/imports/api/presentations'; import PresentationAreaService from '/imports/ui/components/presentation/service'; import Poll from '/imports/ui/components/poll/component'; import Service from './service'; +const CHAT_CONFIG = Meteor.settings.public.chat; +const PUBLIC_CHAT_KEY = CHAT_CONFIG.public_id; + const PollContainer = ({ ...props }) => <Poll {...props} />; export default withTracker(() => { @@ -18,9 +20,13 @@ export default withTracker(() => { const currentSlide = PresentationAreaService.getCurrentSlide(currentPresentation.podId); - const startPoll = type => makeCall('startPoll', type, currentSlide.id); + const pollId = currentSlide ? currentSlide.id : PUBLIC_CHAT_KEY; + + const startPoll = type => makeCall('startPoll', type, pollId); + + const startCustomPoll = (type, answers) => makeCall('startPoll', type, pollId, answers); - const startCustomPoll = (type, answers) => makeCall('startPoll', type, currentSlide.id, answers); + const stopPoll = () => makeCall('stopPoll'); return { currentSlide, @@ -28,7 +34,7 @@ export default withTracker(() => { pollTypes: Service.pollTypes, startPoll, startCustomPoll, - stopPoll: Service.stopPoll, + stopPoll, publishPoll: Service.publishPoll, currentPoll: Service.currentPoll(), resetPollPanel: Session.get('resetPollPanel') || false, diff --git a/bigbluebutton-html5/imports/ui/components/poll/live-result/component.jsx b/bigbluebutton-html5/imports/ui/components/poll/live-result/component.jsx index 1070f028709b916140584a8b3e360e9652c4aaf2..0aa6f2ace9fb8bc84bb35c61ec1e8962807ad0b4 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/live-result/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/poll/live-result/component.jsx @@ -194,12 +194,15 @@ class LiveResult extends PureComponent { Session.set('pollInitiated', false); Service.publishPoll(); const { answers, numRespondents } = currentPoll; - + let responded = 0; let resultString = 'bbb-published-poll-\n'; - answers.forEach((item) => { - const pct = Math.round(item.numVotes / numRespondents * 100); + answers.map((item) => { + responded += item.numVotes; + return item; + }).map((item) => { + const numResponded = responded === numRespondents ? numRespondents : responded; + const pct = Math.round(item.numVotes / numResponded * 100); const pctFotmatted = `${Number.isNaN(pct) ? 0 : pct}%`; - resultString += `${item.key}: ${item.numVotes || 0} | ${pctFotmatted}\n`; }); diff --git a/bigbluebutton-html5/imports/ui/components/poll/live-result/styles.scss b/bigbluebutton-html5/imports/ui/components/poll/live-result/styles.scss index 442e4cfbfdee2d18988d313878f82e2273796426..76d9992fc6dedbe011657e1e925e48573dc0b407 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/live-result/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/poll/live-result/styles.scss @@ -1,5 +1,3 @@ -@import "/imports/ui/stylesheets/variables/_all"; - :root { --poll-stats-border-color: #d4d9df; --poll-stats-option-width: 4em; diff --git a/bigbluebutton-html5/imports/ui/components/poll/service.js b/bigbluebutton-html5/imports/ui/components/poll/service.js index bd853982528d7f5bccab1a1eb0190fed87fbe636..9f3fca29061212137c28e0fd62d850dab5e8eb4f 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/service.js +++ b/bigbluebutton-html5/imports/ui/components/poll/service.js @@ -59,7 +59,7 @@ const sendGroupMessage = (message) => { color: '0', correlationId: `${PUBLIC_CHAT_SYSTEM_ID}-${Date.now()}`, sender: { - id: PUBLIC_CHAT_SYSTEM_ID, + id: Auth.userID, name: '', }, message, @@ -69,9 +69,11 @@ const sendGroupMessage = (message) => { }; export default { - amIPresenter: () => Users.findOne({ userId: Auth.userID }, { fields: { presenter: 1 } }).presenter, + amIPresenter: () => Users.findOne( + { userId: Auth.userID }, + { fields: { presenter: 1 } }, + ).presenter, pollTypes, - stopPoll: () => makeCall('stopPoll', Auth.userId), currentPoll: () => Polls.findOne({ meetingId: Auth.meetingID }), pollAnswerIds, sendGroupMessage, diff --git a/bigbluebutton-html5/imports/ui/components/poll/styles.scss b/bigbluebutton-html5/imports/ui/components/poll/styles.scss index a1bcc9b360e3d24c1468605b9e41776d91bc9ee1..f129c63e09414791a4900b495d34f6fde1788afe 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/poll/styles.scss @@ -1,6 +1,6 @@ -@import "/imports/ui/stylesheets/variables/_all"; @import "/imports/ui/stylesheets/mixins/focus"; @import "/imports/ui/stylesheets/mixins/_indicators"; +@import "/imports/ui/stylesheets/variables/placeholders"; :root { --poll-column-amount: 2; @@ -188,7 +188,7 @@ &:hover, &:focus { - @include highContrastOutline(); + @extend %highContrastOutline; } &:focus { diff --git a/bigbluebutton-html5/imports/ui/components/polling/styles.scss b/bigbluebutton-html5/imports/ui/components/polling/styles.scss index cfa96477092cac2d489b95e400256c2a640173a8..0e806213fc28fbe5d71c1b57541ed31a19af07d8 100644 --- a/bigbluebutton-html5/imports/ui/components/polling/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/polling/styles.scss @@ -1,4 +1,4 @@ -@import "../../stylesheets/variables/_all"; +@import "../../stylesheets/variables/breakpoints"; :root { --col-amount: 2; diff --git a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx index ce757f52583fdd4b2cfd50741b58fb68feeea05a..104605e61994d4fa532fba63f4c91d327b96faa2 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import WhiteboardOverlayContainer from '/imports/ui/components/whiteboard/whiteboard-overlay/container'; import WhiteboardToolbarContainer from '/imports/ui/components/whiteboard/whiteboard-toolbar/container'; import { HUNDRED_PERCENT, MAX_PERCENT } from '/imports/utils/slideCalcUtils'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import { toast } from 'react-toastify'; import PresentationToolbarContainer from './presentation-toolbar/container'; import CursorWrapperContainer from './cursor/cursor-wrapper-container/container'; @@ -11,6 +11,7 @@ import AnnotationGroupContainer from '../whiteboard/annotation-group/container'; import PresentationOverlayContainer from './presentation-overlay/container'; import Slide from './slide/component'; import { styles } from './styles.scss'; +import toastStyles from '/imports/ui/components/toast/styles'; import MediaService, { shouldEnableSwapLayout } from '../media/service'; import PresentationCloseButton from './presentation-close-button/component'; import DownloadPresentationButton from './download-presentation-button/component'; @@ -18,6 +19,7 @@ import FullscreenService from '../fullscreen-button/service'; import FullscreenButtonContainer from '../fullscreen-button/container'; import { withDraggableConsumer } from '../media/webcam-draggable-overlay/context'; import Icon from '/imports/ui/components/icon/component'; +import { withLayoutConsumer } from '/imports/ui/components/layout/context'; const intlMessages = defineMessages({ presentationLabel: { @@ -28,6 +30,22 @@ const intlMessages = defineMessages({ id: 'app.presentation.notificationLabel', description: 'label displayed in toast when presentation switches', }, + downloadLabel: { + id: 'app.presentation.downloadLabel', + description: 'label for downloadable presentations', + }, + slideContentStart: { + id: 'app.presentation.startSlideContent', + description: 'Indicate the slide content start', + }, + slideContentEnd: { + id: 'app.presentation.endSlideContent', + description: 'Indicate the slide content end', + }, + noSlideContent: { + id: 'app.presentation.emptySlideContent', + description: 'No content available for slide', + }, }); const ALLOW_FULLSCREEN = Meteor.settings.public.app.allowFullscreen; @@ -54,6 +72,10 @@ class PresentationArea extends PureComponent { this.panAndZoomChanger = this.panAndZoomChanger.bind(this); this.fitToWidthHandler = this.fitToWidthHandler.bind(this); this.onFullscreenChange = this.onFullscreenChange.bind(this); + this.getPresentationSizesAvailable = this.getPresentationSizesAvailable.bind(this); + this.handleResize = this.handleResize.bind(this); + + this.onResize = () => setTimeout(this.handleResize.bind(this), 0); this.renderCurrentPresentationToast = this.renderCurrentPresentationToast.bind(this); } @@ -83,18 +105,41 @@ class PresentationArea extends PureComponent { } componentDidMount() { - // adding an event listener to scale the whiteboard on 'resize' events sent by chat/userlist etc - window.addEventListener('resize', this.onResize); this.getInitialPresentationSizes(); this.refPresentationContainer.addEventListener('fullscreenchange', this.onFullscreenChange); + window.addEventListener('resize', this.onResize, false); + window.addEventListener('layoutSizesSets', this.onResize, false); + window.addEventListener('webcamAreaResize', this.handleResize, false); + + const { slidePosition, layoutContextDispatch } = this.props; + + let currWidth = 0; + let currHeight = 0; + + if (slidePosition) { + currWidth = slidePosition.width; + currHeight = slidePosition.height; + } + + layoutContextDispatch({ + type: 'setPresentationSlideSize', + value: { + width: currWidth, + height: currHeight, + }, + }); - const { slidePosition, webcamDraggableDispatch } = this.props; - const { width: currWidth, height: currHeight } = slidePosition; if (currWidth > currHeight || currWidth === currHeight) { - webcamDraggableDispatch({ type: 'setOrientationToLandscape' }); + layoutContextDispatch({ + type: 'setPresentationOrientation', + value: 'landscape', + }); } if (currHeight > currWidth) { - webcamDraggableDispatch({ type: 'setOrientationToPortrait' }); + layoutContextDispatch({ + type: 'setPresentationOrientation', + value: 'portrait', + }); } } @@ -102,37 +147,86 @@ class PresentationArea extends PureComponent { const { currentPresentation, slidePosition, - webcamDraggableDispatch, layoutSwapped, currentSlide, publishedPoll, isViewer, toggleSwapLayout, restoreOnUpdate, + layoutContextDispatch, + layoutContextState, + userIsPresenter, } = this.props; + const { numUsersVideo } = layoutContextState; + const { layoutContextState: prevLayoutContextState } = prevProps; + const { + numUsersVideo: prevNumUsersVideo, + } = prevLayoutContextState; + + if (numUsersVideo !== prevNumUsersVideo) { + this.onResize(); + } + + if (prevProps.slidePosition.id !== slidePosition.id) { + window.dispatchEvent(new Event('slideChanged')); + } + const { width: prevWidth, height: prevHeight } = prevProps.slidePosition; const { width: currWidth, height: currHeight } = slidePosition; if (prevWidth !== currWidth || prevHeight !== currHeight) { + layoutContextDispatch({ + type: 'setPresentationSlideSize', + value: { + width: currWidth, + height: currHeight, + }, + }); if (currWidth > currHeight || currWidth === currHeight) { - webcamDraggableDispatch({ type: 'setOrientationToLandscape' }); + layoutContextDispatch({ + type: 'setPresentationOrientation', + value: 'landscape', + }); } if (currHeight > currWidth) { - webcamDraggableDispatch({ type: 'setOrientationToPortrait' }); + layoutContextDispatch({ + type: 'setPresentationOrientation', + value: 'portrait', + }); } } - if (prevProps.currentPresentation.name !== currentPresentation.name) { + const downloadableOn = !prevProps.currentPresentation.downloadable + && currentPresentation.downloadable; + + const shouldCloseToast = !(currentPresentation.downloadable && !userIsPresenter); + + if ( + prevProps.currentPresentation.name !== currentPresentation.name + || (downloadableOn && !userIsPresenter) + ) { if (this.currentPresentationToastId) { - return toast.update(this.currentPresentationToastId, { + toast.update(this.currentPresentationToastId, { + autoClose: shouldCloseToast, render: this.renderCurrentPresentationToast(), }); + } else { + this.currentPresentationToastId = toast(this.renderCurrentPresentationToast(), { + onClose: () => { this.currentPresentationToastId = null; }, + autoClose: shouldCloseToast, + className: toastStyles.actionToast, + }); } + } + + const downloadableOff = prevProps.currentPresentation.downloadable + && !currentPresentation.downloadable; - this.currentPresentationToastId = toast(this.renderCurrentPresentationToast(), { - onClose: () => { this.currentPresentationToastId = null; }, + if (this.currentPresentationToastId && downloadableOff) { + toast.update(this.currentPresentationToastId, { autoClose: true, + render: this.renderCurrentPresentationToast(), }); } @@ -141,23 +235,25 @@ class PresentationArea extends PureComponent { const positionChanged = slidePosition.viewBoxHeight !== prevProps.slidePosition.viewBoxHeight || slidePosition.viewBoxWidth !== prevProps.slidePosition.viewBoxWidth; const pollPublished = publishedPoll && !prevProps.publishedPoll; - if (slideChanged || positionChanged || pollPublished || presentationChanged) { + if (slideChanged || positionChanged || pollPublished) { toggleSwapLayout(); } } } componentWillUnmount() { - window.removeEventListener('resize', this.onResize); + window.removeEventListener('resize', this.onResize, false); + window.removeEventListener('layoutSizesSets', this.onResize, false); this.refPresentationContainer.removeEventListener('fullscreenchange', this.onFullscreenChange); } onFullscreenChange() { + const { layoutContextDispatch } = this.props; const { isFullscreen } = this.state; const newIsFullscreen = FullscreenService.isFullScreen(this.refPresentationContainer); if (isFullscreen !== newIsFullscreen) { this.setState({ isFullscreen: newIsFullscreen }); - window.dispatchEvent(new Event('resize')); + layoutContextDispatch({ type: 'setPresentationFullscreen', value: newIsFullscreen }); } } @@ -178,25 +274,25 @@ class PresentationArea extends PureComponent { } getPresentationSizesAvailable() { - const { userIsPresenter, multiUser } = this.props; - const { refPresentationArea, refWhiteboardArea } = this; - const presentationSizes = {}; - - if (refPresentationArea && refWhiteboardArea) { - // By default presentation sizes are equal to the sizes of the refPresentationArea - // direct parent of the svg wrapper - let { clientWidth, clientHeight } = refPresentationArea; - - // if a user is a presenter - this means there is a whiteboard toolbar on the right - // and we have to get the width/height of the refWhiteboardArea - // (inner hidden div with absolute position) - if (userIsPresenter || multiUser) { - ({ clientWidth, clientHeight } = refWhiteboardArea); - } + const { layoutContextState } = this.props; + const { + presentationAreaSize, + webcamsAreaResizing, + mediaBounds, + tempWebcamsAreaSize, + webcamsPlacement, + } = layoutContextState; + const presentationSizes = { + presentationAreaWidth: 0, + presentationAreaHeight: 0, + }; - presentationSizes.presentationAreaHeight = clientHeight - this.getToolbarHeight(); - presentationSizes.presentationAreaWidth = clientWidth; - } + presentationSizes.presentationAreaWidth = webcamsAreaResizing && (webcamsPlacement === 'left' || webcamsPlacement === 'right') + ? mediaBounds.width - tempWebcamsAreaSize.width + : presentationAreaSize.width; + presentationSizes.presentationAreaHeight = webcamsAreaResizing && (webcamsPlacement === 'top' || webcamsPlacement === 'bottom') + ? mediaBounds.height - tempWebcamsAreaSize.height - (this.getToolbarHeight() || 0) - 30 + : presentationAreaSize.height - (this.getToolbarHeight() || 0); return presentationSizes; } @@ -417,6 +513,7 @@ class PresentationArea extends PureComponent { // renders the whole presentation area renderPresentationArea(svgDimensions, viewBoxDimensions) { const { + intl, podId, currentSlide, slidePosition, @@ -440,6 +537,7 @@ class PresentationArea extends PureComponent { const { imageUri, + content, } = currentSlide; let viewBoxPosition; @@ -467,24 +565,29 @@ class PresentationArea extends PureComponent { const svgViewBox = `${viewBoxPosition.x} ${viewBoxPosition.y} ` + `${viewBoxDimensions.width} ${Number.isNaN(viewBoxDimensions.height) ? 0 : viewBoxDimensions.height}`; + const slideContent = content ? `${intl.formatMessage(intlMessages.slideContentStart)} + ${content} + ${intl.formatMessage(intlMessages.slideContentEnd)}` : intl.formatMessage(intlMessages.noSlideContent); + return ( <div style={{ position: 'absolute', - width: svgDimensions.width, - height: svgDimensions.height, + width: svgDimensions.width < 0 ? 0 : svgDimensions.width, + height: svgDimensions.height < 0 ? 0 : svgDimensions.height, textAlign: 'center', display: layoutSwapped ? 'none' : 'block', }} > + <span id="currentSlideText" className={styles.visuallyHidden}>{slideContent}</span> {this.renderPresentationClose()} {this.renderPresentationDownload()} {this.renderPresentationFullscreen()} <svg key={currentSlide.id} data-test="whiteboard" - width={svgDimensions.width} - height={svgDimensions.height} + width={svgDimensions.width < 0 ? 0 : svgDimensions.width} + height={svgDimensions.height < 0 ? 0 : svgDimensions.height} ref={(ref) => { if (ref != null) { this.svggroup = ref; } }} viewBox={svgViewBox} version="1.1" @@ -619,7 +722,10 @@ class PresentationArea extends PureComponent { } renderCurrentPresentationToast() { - const { intl, currentPresentation } = this.props; + const { + intl, currentPresentation, userIsPresenter, downloadPresentationUri, + } = this.props; + const { downloadable } = currentPresentation; return ( <div className={styles.innerToastWrapper}> @@ -628,10 +734,28 @@ class PresentationArea extends PureComponent { <Icon iconName="presentation" /> </div> </div> + <div className={styles.toastTextContent} data-test="toastSmallMsg"> <div>{`${intl.formatMessage(intlMessages.changeNotification)}`}</div> <div className={styles.presentationName}>{`${currentPresentation.name}`}</div> </div> + + {downloadable && !userIsPresenter + ? ( + <span className={styles.toastDownload}> + <div className={toastStyles.separator} /> + <a + className={styles.downloadBtn} + aria-label={`${intl.formatMessage(intlMessages.downloadLabel)} ${currentPresentation.name}`} + href={downloadPresentationUri} + target="_blank" + rel="noopener noreferrer" + > + {intl.formatMessage(intlMessages.downloadLabel)} + </a> + </span> + ) : null + } </div> ); } @@ -645,8 +769,8 @@ class PresentationArea extends PureComponent { const { showSlide, - fitToWidth, - presentationAreaWidth, + // fitToWidth, + // presentationAreaWidth, localPosition, } = this.state; @@ -677,16 +801,7 @@ class PresentationArea extends PureComponent { let toolbarWidth = 0; if (this.refWhiteboardArea) { - if (svgWidth === presentationAreaWidth - || presentationAreaWidth <= 400 - || fitToWidth === true) { - toolbarWidth = '100%'; - } else if (svgWidth <= 400 - && presentationAreaWidth > 400) { - toolbarWidth = '400px'; - } else { - toolbarWidth = svgWidth; - } + toolbarWidth = svgWidth; } return ( @@ -736,10 +851,10 @@ class PresentationArea extends PureComponent { } } -export default injectIntl(withDraggableConsumer(PresentationArea)); +export default injectIntl(withDraggableConsumer(withLayoutConsumer(PresentationArea))); PresentationArea.propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, podId: PropTypes.string.isRequired, // Defines a boolean value to detect whether a current user is a presenter userIsPresenter: PropTypes.bool.isRequired, diff --git a/bigbluebutton-html5/imports/ui/components/presentation/container.jsx b/bigbluebutton-html5/imports/ui/components/presentation/container.jsx index d69440a982e36905c05a3514cbee2879b006b9a8..398d14e8a2b19472568ca66a25c76d7529801060 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/container.jsx @@ -3,6 +3,7 @@ import { withTracker } from 'meteor/react-meteor-data'; import MediaService, { getSwapLayout, shouldEnableSwapLayout } from '/imports/ui/components/media/service'; import { notify } from '/imports/ui/services/notification'; import PresentationAreaService from './service'; +import { Slides } from '/imports/api/slides'; import PresentationArea from './component'; import PresentationToolbarService from './presentation-toolbar/service'; import Auth from '/imports/ui/services/auth'; @@ -16,6 +17,10 @@ const PresentationAreaContainer = ({ presentationPodIds, mountPresentationArea, mountPresentationArea && <PresentationArea {...props} /> ); +const APP_CONFIG = Meteor.settings.public.app; +const PRELOAD_NEXT_SLIDE = APP_CONFIG.preloadNextSlides; +const fetchedpresentation = {}; + export default withTracker(({ podId }) => { const currentSlide = PresentationAreaService.getCurrentSlide(podId); const presentationIsDownloadable = PresentationAreaService.isPresentationDownloadable(podId); @@ -33,6 +38,35 @@ export default withTracker(({ podId }) => { id: slideId, } = currentSlide; slidePosition = PresentationAreaService.getSlidePosition(podId, presentationId, slideId); + if (PRELOAD_NEXT_SLIDE && !fetchedpresentation[presentationId]) { + fetchedpresentation[presentationId] = { + canFetch: true, + fetchedSlide: {}, + }; + } + const currentSlideNum = currentSlide.num; + const presentation = fetchedpresentation[presentationId]; + + if (PRELOAD_NEXT_SLIDE && !presentation.fetchedSlide[currentSlide.num + PRELOAD_NEXT_SLIDE] && presentation.canFetch) { + const slidesToFetch = Slides.find({ + podId, + presentationId, + num: { + $in: Array(PRELOAD_NEXT_SLIDE).fill(1).map((v, idx) => currentSlideNum + (idx + 1)), + }, + }).fetch(); + + const promiseImageGet = slidesToFetch + .filter(s => !fetchedpresentation[presentationId].fetchedSlide[s.num]) + .map(async (slide) => { + if (presentation.canFetch) presentation.canFetch = false; + const image = await fetch(slide.imageUri); + if (image.ok) { + presentation.fetchedSlide[slide.num] = true; + } + }); + Promise.all(promiseImageGet).then(() => presentation.canFetch = true); + } } return { currentSlide, diff --git a/bigbluebutton-html5/imports/ui/components/presentation/cursor/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/cursor/component.jsx index 85a74fee86897c799d7c29d24f804f48933dcdc6..aa64b280698bf47888a67f24eaf76a0a8c542651 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/cursor/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/cursor/component.jsx @@ -38,7 +38,7 @@ export default class Cursor extends Component { return obj; } - static getScaledSizes(props) { + static getScaledSizes(props, state) { // TODO: This might need to change for the use case of fit-to-width portrait // slides in non-presenter view. Some elements are still shrinking. const scaleFactor = props.widthRatio / props.physicalWidthRatio; @@ -58,14 +58,25 @@ export default class Cursor extends Component { yOffset: props.cursorLabelBox.yOffset * scaleFactor, // making width and height a little bit larger than the size of the text // received from BBox, so that the text didn't touch the border - width: (props.labelBoxWidth + 3) * scaleFactor, - height: (props.labelBoxHeight + 3) * scaleFactor, + width: (state.labelBoxWidth + 3) * scaleFactor, + height: (state.labelBoxHeight + 3) * scaleFactor, strokeWidth: props.cursorLabelBox.labelBoxStrokeWidth * scaleFactor, }, }; } - componentWillMount() { + constructor(props) { + super(props); + this.state = { + scaledSizes: null, + labelBoxWidth: 0, + labelBoxHeight: 0, + }; + + this.setLabelBoxDimensions = this.setLabelBoxDimensions.bind(this); + } + + componentDidMount() { const { cursorX, cursorY, @@ -75,8 +86,9 @@ export default class Cursor extends Component { isMultiUser, } = this.props; - // setting the initial cursor info - this.scaledSizes = Cursor.getScaledSizes(this.props); + this.setState({ + scaledSizes: Cursor.getScaledSizes(this.props, this.state), + }); this.cursorCoordinate = Cursor.getCursorCoordinates( cursorX, cursorY, @@ -87,66 +99,83 @@ export default class Cursor extends Component { const { fill, displayLabel } = Cursor.getFillAndLabel(presenter, isMultiUser); this.fill = fill; this.displayLabel = displayLabel; - } - - componentDidMount() { // we need to find the BBox of the text, so that we could set a proper border box arount it - this.calculateCursorLabelBoxDimensions(); } - componentWillReceiveProps(nextProps) { + componentDidUpdate(prevProps, prevState) { + const { + scaledSizes, + } = this.state; + if (!prevState.scaledSizes && scaledSizes) { + this.calculateCursorLabelBoxDimensions(); + } + const { presenter, isMultiUser, widthRatio, physicalWidthRatio, - labelBoxWidth, - labelBoxHeight, cursorX, cursorY, + slideWidth, + slideHeight, } = this.props; + const { + labelBoxWidth, + labelBoxHeight, + } = this.state; + + const { + labelBoxWidth: prevLabelBoxWidth, + labelBoxHeight: prevLabelBoxHeight, + } = prevState; - if (presenter !== nextProps.presenter || isMultiUser !== nextProps.isMultiUser) { + if (presenter !== prevProps.presenter || isMultiUser !== prevProps.isMultiUser) { const { fill, displayLabel } = Cursor.getFillAndLabel( - nextProps.presenter, - nextProps.isMultiUser, + presenter, + isMultiUser, ); this.displayLabel = displayLabel; this.fill = fill; } - if ((widthRatio !== nextProps.widthRatio - || physicalWidthRatio !== nextProps.physicalWidthRatio) - || (labelBoxWidth !== nextProps.labelBoxWidth - || labelBoxHeight !== nextProps.labelBoxHeight)) { - this.scaledSizes = Cursor.getScaledSizes(nextProps); + if ((widthRatio !== prevProps.widthRatio + || physicalWidthRatio !== prevProps.physicalWidthRatio) + || (labelBoxWidth !== prevLabelBoxWidth + || labelBoxHeight !== prevLabelBoxHeight)) { + this.setState({ + scaledSizes: Cursor.getScaledSizes(this.props, this.state), + }); } - if (cursorX !== nextProps.cursorX || cursorY !== nextProps.cursorY) { + if (cursorX !== prevProps.cursorX || cursorY !== prevProps.cursorY) { const cursorCoordinate = Cursor.getCursorCoordinates( - nextProps.cursorX, - nextProps.cursorY, - nextProps.slideWidth, - nextProps.slideHeight, + cursorX, + cursorY, + slideWidth, + slideHeight, ); this.cursorCoordinate = cursorCoordinate; } } + setLabelBoxDimensions(labelBoxWidth, labelBoxHeight) { + this.setState({ + labelBoxWidth, + labelBoxHeight, + }); + } + // this function retrieves the text node, measures its BBox and sets the size for the outer box calculateCursorLabelBoxDimensions() { - const { - setLabelBoxDimensions, - } = this.props; - let labelBoxWidth = 0; let labelBoxHeight = 0; + if (this.cursorLabelRef) { const { width, height } = this.cursorLabelRef.getBBox(); const { widthRatio, physicalWidthRatio, cursorLabelBox } = this.props; labelBoxWidth = Cursor.invertScale(width, widthRatio, physicalWidthRatio); labelBoxHeight = Cursor.invertScale(height, widthRatio, physicalWidthRatio); - // if the width of the text node is bigger than the maxSize - set the width to maxWidth if (labelBoxWidth > cursorLabelBox.maxWidth) { labelBoxWidth = cursorLabelBox.maxWidth; @@ -154,26 +183,30 @@ export default class Cursor extends Component { } // updating labelBoxWidth and labelBoxHeight in the container, which then passes it down here - setLabelBoxDimensions(labelBoxWidth, labelBoxHeight); + this.setLabelBoxDimensions(labelBoxWidth, labelBoxHeight); } render() { + const { + scaledSizes, + } = this.state; const { cursorId, userName, isRTL, } = this.props; - + const { cursorCoordinate, fill, } = this; - + + if (!scaledSizes) return null; const { cursorLabelBox, cursorLabelText, finalRadius, - } = this.scaledSizes; + } = scaledSizes; const { x, @@ -191,7 +224,7 @@ export default class Cursor extends Component { <circle cx={x} cy={y} - r={finalRadius === Infinity ? 0 : finalRadius} + r={finalRadius} fill={fill} fillOpacity="0.6" /> @@ -292,17 +325,6 @@ Cursor.propTypes = { fontSize: PropTypes.number.isRequired, }), - // Defines the width of the label box - labelBoxWidth: PropTypes.number.isRequired, - // Defines the height of the label box - labelBoxHeight: PropTypes.number.isRequired, - - // Defines the function, which sets the state for the label box and passes it back down - // we need it, since we need to render the text first -> measure its dimensions -> - // set proper width and height of the border box -> pass it down -> - // catch in the 'componentWillReceiveProps' -> apply new values - setLabelBoxDimensions: PropTypes.func.isRequired, - // Defines the direction the client text should be displayed isRTL: PropTypes.bool.isRequired, }; diff --git a/bigbluebutton-html5/imports/ui/components/presentation/cursor/container.jsx b/bigbluebutton-html5/imports/ui/components/presentation/cursor/container.jsx index 4938b194f66975db5376fa0b28fbb4e1a32a973a..a9743de25a7105172fc9b6b66d61f5db221fe98d 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/cursor/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/cursor/container.jsx @@ -6,33 +6,15 @@ import Cursor from './component'; class CursorContainer extends Component { - constructor() { - super(); - this.state = { - labelBoxWidth: 0, - labelBoxHeight: 0, - }; - this.setLabelBoxDimensions = this.setLabelBoxDimensions.bind(this); - } - - setLabelBoxDimensions(labelBoxWidth, labelBoxHeight) { - this.setState({ - labelBoxWidth, - labelBoxHeight, - }); - } render() { const { cursorX, cursorY } = this.props; - const { labelBoxWidth, labelBoxHeight } = this.state; if (cursorX > 0 && cursorY > 0) { return ( <Cursor cursorX={cursorX} cursorY={cursorY} - labelBoxWidth={labelBoxWidth} - labelBoxHeight={labelBoxHeight} setLabelBoxDimensions={this.setLabelBoxDimensions} {...this.props} /> diff --git a/bigbluebutton-html5/imports/ui/components/presentation/default-content/styles.scss b/bigbluebutton-html5/imports/ui/components/presentation/default-content/styles.scss index d80f0b9aa8f0a5bb923fe8ebb8fc99e375abf073..7dd6834696b44764d8cc3c032aa47e2cfecb6145 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/default-content/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/presentation/default-content/styles.scss @@ -1,4 +1,4 @@ -@import "../../../stylesheets/variables/_all"; +@import "../../../stylesheets/variables/breakpoints"; .contentRatio { position: relative; diff --git a/bigbluebutton-html5/imports/ui/components/presentation/download-presentation-button/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/download-presentation-button/component.jsx index ceb8eae71a2e8f29acd6a5d6752f3d1edc3d598e..6c096167d1414cef07854d7fc4346021ebba2241 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/download-presentation-button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/download-presentation-button/component.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import Button from '/imports/ui/components/button/component'; import cx from 'classnames'; import PropTypes from 'prop-types'; @@ -13,7 +13,7 @@ const intlMessages = defineMessages({ }); const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, handleDownloadPresentation: PropTypes.func.isRequired, dark: PropTypes.bool, }; diff --git a/bigbluebutton-html5/imports/ui/components/presentation/download-presentation-button/styles.scss b/bigbluebutton-html5/imports/ui/components/presentation/download-presentation-button/styles.scss index 2387f0bab9df2163a711a20490cce04a833a333c..600efc568ceded07adae9e70bb33a64d7e50fedc 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/download-presentation-button/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/presentation/download-presentation-button/styles.scss @@ -1,11 +1,3 @@ -@import '/imports/ui/stylesheets/variables/_all'; - -:root { - ::-webkit-media-controls { - display:none !important; - } -} - .wrapper { position: absolute; left: 0; @@ -46,4 +38,4 @@ .dark .button span i { color: var(--color-black); -} \ No newline at end of file +} diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/styles.scss b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/styles.scss index af0dcfca403f38a383c79456d91fab62e65b2853..31c92c76a192e4b54602c0129fb850477e8b0748 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/styles.scss @@ -1,5 +1,5 @@ -@import "/imports/ui/stylesheets/variables/_all"; @import "/imports/ui/stylesheets/mixins/_indicators"; +@import "/imports/ui/stylesheets/variables/placeholders"; :root { --whiteboard-toolbar-padding-sm: .3rem; @@ -163,11 +163,11 @@ } &:hover { - @include highContrastOutline(); + @extend %highContrastOutline; } &:focus { - @include highContrastOutline(); + @extend %highContrastOutline; outline-style: solid; } } diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/zoom-tool/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/zoom-tool/component.jsx index 9dccce65b4df69686a5b7e679a0e244bcecc2bbf..78e30a3e047550d78c39ff225c03d480744e938d 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/zoom-tool/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/zoom-tool/component.jsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import Button from '/imports/ui/components/button/component'; import cx from 'classnames'; import { styles } from '../styles.scss'; @@ -231,7 +231,7 @@ class ZoomTool extends PureComponent { } const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, zoomValue: PropTypes.number.isRequired, change: PropTypes.func.isRequired, minBound: PropTypes.number.isRequired, diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/component.jsx index cba3c2b25da6098b7f0d0b545d0039aa4d8621b1..7c1b6a6e7933876d73dd9ca806a2f93bb1e4652a 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/component.jsx @@ -1,6 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { intlShape, injectIntl, defineMessages } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import cx from 'classnames'; import Button from '/imports/ui/components/button/component'; import Checkbox from '/imports/ui/components/checkbox/component'; @@ -21,7 +21,8 @@ const isMobileBrowser = (BROWSER_RESULTS ? BROWSER_RESULTS.mobile : false) : false); const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, + mountModal: PropTypes.func.isRequired, defaultFileName: PropTypes.string.isRequired, fileSizeMin: PropTypes.number.isRequired, fileSizeMax: PropTypes.number.isRequired, diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/styles.scss b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/styles.scss index 147a7fee2fe0f8e16bf4722abb985e43080438a3..2e6618be529e846e4bb9b0dcafff5ffe8b4835d7 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/styles.scss @@ -1,5 +1,6 @@ -@import "/imports/ui/stylesheets/variables/_all"; @import "/imports/ui/stylesheets/mixins/_scrollable"; +@import "/imports/ui/stylesheets/variables/placeholders"; +@import "/imports/ui/stylesheets/variables/breakpoints"; :root { --uploadIconSize: 2.286rem; diff --git a/bigbluebutton-html5/imports/ui/components/presentation/styles.scss b/bigbluebutton-html5/imports/ui/components/presentation/styles.scss index d7efec3bc5d2f1fe856e3ca6c5ddc5eb6f650db7..4ef5de6c27604f34f7a5a09d0ba07da390eab7bb 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/presentation/styles.scss @@ -1,11 +1,3 @@ -@import "/imports/ui/stylesheets/variables/_all"; -@import "/imports/ui/components/whiteboard/whiteboard-toolbar/styles"; - -:root { - --innerToastWidth: 17rem; - --iconWrapperSize: 2rem; -} - .enter { opacity: 0.01; } @@ -93,14 +85,17 @@ } .innerToastWrapper { - display: flex; - flex-direction: row; width: var(--innerToastWidth); } .toastTextContent { position: relative; overflow: hidden; + margin-top: var(--sm-padding-y); + + > div:first-of-type { + font-weight: bold; + } } .presentationName { @@ -108,6 +103,13 @@ overflow: hidden; } +.toastMessage { + font-size: var(--font-size-small); + margin-top: var(--toast-margin); + overflow: hidden; + text-overflow: ellipsis; +} + .toastIcon { margin-right: var(--sm-padding-x); [dir="rtl"] & { @@ -118,21 +120,37 @@ .iconWrapper { background-color: var(--color-primary); - width: var(--iconWrapperSize); - height: var(--iconWrapperSize); + width: var(--toast-icon-side); + height: var(--toast-icon-side); border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; > i { position: relative; - left: var(--md-padding-y); - top: var(--sm-padding-y); - font-size: var(--font-size-base); color: var(--color-white); + font-size: var(--font-size-larger); + } +} + +.toastDownload { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; - [dir="rtl"] & { - left: 0; - right: var(--md-padding-y); - } + a { + color: var(--color-primary); + cursor: pointer; + text-decoration: none; + + &:focus, + &:hover, + &:active { + color: var(--color-primary); + box-shadow: 0; + } } } diff --git a/bigbluebutton-html5/imports/ui/components/recording/component.jsx b/bigbluebutton-html5/imports/ui/components/recording/component.jsx index 48a5ac5a01fc66231bd0318db930400e0274fe7e..199594c08c40331b2a243fac56a35ca7b6c51684 100755 --- a/bigbluebutton-html5/imports/ui/components/recording/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/recording/component.jsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import Button from '/imports/ui/components/button/component'; import Modal from '/imports/ui/components/modal/simple/component'; import { styles } from './styles'; @@ -37,7 +37,7 @@ const intlMessages = defineMessages({ }); const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, closeModal: PropTypes.func.isRequired, toggleRecording: PropTypes.func.isRequired, recordingTime: PropTypes.number, diff --git a/bigbluebutton-html5/imports/ui/components/recording/styles.scss b/bigbluebutton-html5/imports/ui/components/recording/styles.scss index 0b008cd872b20e369b8edab227c5f8342e630936..08277f9c20f5d84ea1a69987a79e68b9db0c8561 100755 --- a/bigbluebutton-html5/imports/ui/components/recording/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/recording/styles.scss @@ -1,11 +1,5 @@ -@import '/imports/ui/stylesheets/mixins/focus'; -@import '/imports/ui/stylesheets/variables/_all'; @import "/imports/ui/components/modal/simple/styles"; -:root { - --description-margin: 3.5rem; -} - .title { color: var(--color-gray-dark); font-weight: var(--headings-font-weight); diff --git a/bigbluebutton-html5/imports/ui/components/screenshare/component.jsx b/bigbluebutton-html5/imports/ui/components/screenshare/component.jsx index 5d7753857a8e750ff9e3c10b11a7b43ebd3e38f1..64fba6fd0f5740e51fe8a71f3223f54ba537c3b1 100755 --- a/bigbluebutton-html5/imports/ui/components/screenshare/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/screenshare/component.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import PropTypes from 'prop-types'; import _ from 'lodash'; import FullscreenService from '../fullscreen-button/service'; @@ -48,11 +48,11 @@ class ScreenshareComponent extends React.Component { window.addEventListener('screensharePlayFailed', this.handlePlayElementFailed); } - componentWillReceiveProps(nextProps) { + componentDidUpdate(prevProps) { const { isPresenter, unshareScreen, } = this.props; - if (isPresenter && !nextProps.isPresenter) { + if (isPresenter && !prevProps.isPresenter) { unshareScreen(); } } @@ -176,7 +176,7 @@ class ScreenshareComponent extends React.Component { <video id="screenshareVideo" key="screenshareVideo" - style={{ maxHeight: '100%', width: '100%' }} + style={{ maxHeight: '100%', width: '100%', height: '100%' }} playsInline onLoadedData={this.onVideoLoad} ref={(ref) => { this.videoTag = ref; }} @@ -191,7 +191,7 @@ class ScreenshareComponent extends React.Component { export default injectIntl(ScreenshareComponent); ScreenshareComponent.propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, isPresenter: PropTypes.bool.isRequired, unshareScreen: PropTypes.func.isRequired, presenterScreenshareHasEnded: PropTypes.func.isRequired, diff --git a/bigbluebutton-html5/imports/ui/components/screenshare/service.js b/bigbluebutton-html5/imports/ui/components/screenshare/service.js index f2d71423fad098ddfe40dbf4d343f9b20aec0e67..a246eb71b762ee0dccdc5f96b6ecb3a03eef3a76 100644 --- a/bigbluebutton-html5/imports/ui/components/screenshare/service.js +++ b/bigbluebutton-html5/imports/ui/components/screenshare/service.js @@ -7,18 +7,20 @@ import { tryGenerateIceCandidates } from '/imports/utils/safari-webrtc'; import { stopWatching } from '/imports/ui/components/external-video-player/service'; import Meetings from '/imports/api/meetings'; import Auth from '/imports/ui/services/auth'; +import UserListService from '/imports/ui/components/user-list/service'; // when the meeting information has been updated check to see if it was // screensharing. If it has changed either trigger a call to receive video // and display it, or end the call and hide the video const isVideoBroadcasting = () => { - const ds = Screenshare.findOne({}); + const screenshareEntry = Screenshare.findOne({ meetingId: Auth.meetingID }, + { fields: { 'screenshare.stream': 1 } }); - if (!ds) { + if (!screenshareEntry) { return false; } - return !!ds.screenshare.stream; + return !!screenshareEntry.screenshare.stream; }; // if remote screenshare has been ended disconnect and hide the video stream @@ -28,15 +30,21 @@ const presenterScreenshareHasEnded = () => { KurentoBridge.kurentoExitVideo(); }; +const viewScreenshare = () => { + const amIPresenter = UserListService.isUserPresenter(Auth.userID); + if (!amIPresenter) { + KurentoBridge.kurentoViewScreen(); + } else { + KurentoBridge.kurentoViewLocalPreview(); + } +}; + // if remote screenshare has been started connect and display the video stream const presenterScreenshareHasStarted = () => { - // KurentoBridge.kurentoWatchVideo: references a function in the global - // namespace inside kurento-extension.js that we load dynamically - // WebRTC restrictions may need a capture device permission to release // useful ICE candidates on recvonly/no-gUM peers tryGenerateIceCandidates().then(() => { - KurentoBridge.kurentoWatchVideo(); + viewScreenshare(); }).catch((error) => { logger.error({ logCode: 'screenshare_no_valid_candidate_gum_failure', @@ -46,7 +54,7 @@ const presenterScreenshareHasStarted = () => { }, }, `Forced gUM to release additional ICE candidates failed due to ${error.name}.`); // The fallback gUM failed. Try it anyways and hope for the best. - KurentoBridge.kurentoWatchVideo(); + viewScreenshare(); }); }; diff --git a/bigbluebutton-html5/imports/ui/components/screenshare/styles.scss b/bigbluebutton-html5/imports/ui/components/screenshare/styles.scss index 74e285a439fd4e6b945514c82f577cc1071aac2b..6b66e3b16ec28f2f387231d9947e3d6119b03fe8 100755 --- a/bigbluebutton-html5/imports/ui/components/screenshare/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/screenshare/styles.scss @@ -1,8 +1,7 @@ -@import "/imports/ui/stylesheets/variables/_all"; -@import "/imports/ui/components/video-provider/video-list/styles"; +@import "/imports/ui/components/media/styles"; .connecting { - @extend .connecting; + @extend .connectingSpinner; z-index: -1; background-color: transparent; color: var(--color-white); @@ -17,4 +16,4 @@ position: relative; width: 100%; height: 100%; -} \ No newline at end of file +} diff --git a/bigbluebutton-html5/imports/ui/components/settings/component.jsx b/bigbluebutton-html5/imports/ui/components/settings/component.jsx index dc16059efee8e7ebf4787bc13255c026d0220636..f92a87a31c2772b77c855ba10098e15a47abcd20 100644 --- a/bigbluebutton-html5/imports/ui/components/settings/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/settings/component.jsx @@ -3,7 +3,7 @@ import Modal from '/imports/ui/components/modal/fullscreen/component'; import { Tab, Tabs, TabList, TabPanel, } from 'react-tabs'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import DataSaving from '/imports/ui/components/settings/submenus/data-saving/component'; import Application from '/imports/ui/components/settings/submenus/application/component'; import Notification from '/imports/ui/components/settings/submenus/notification/component'; @@ -62,7 +62,7 @@ const intlMessages = defineMessages({ }); const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, dataSaving: PropTypes.shape({ viewParticipantsWebcams: PropTypes.bool, viewScreenshare: PropTypes.bool, diff --git a/bigbluebutton-html5/imports/ui/components/settings/styles.scss b/bigbluebutton-html5/imports/ui/components/settings/styles.scss index 1c13b8f053705f2aff2e4dab23745a7b4fe6ebe3..6d3aacb6581c329f4168d25df7e7cb67ec8122fd 100644 --- a/bigbluebutton-html5/imports/ui/components/settings/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/settings/styles.scss @@ -1,8 +1,8 @@ -@import "../../stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/breakpoints"; +@import "/imports/ui/stylesheets/variables/placeholders"; .tabs { - display: flex; - flex-flow: row; + @extend %flex-row; justify-content: flex-start; @include mq($small-only) { width: 100%; @@ -11,11 +11,10 @@ } .tabList { - display: flex; - flex-flow: column; + @extend %flex-column; + @extend %no-margin; border: none; padding: 0; - margin: 0; width: calc(100% / 3); @include mq($small-only) { @@ -36,9 +35,8 @@ } .tabSelector { + @extend %flex-row; font-size: 0.9rem; - display: flex; - flex-flow: row; flex: 0 0 auto; justify-content: flex-start; border: none !important; diff --git a/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx b/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx index daf223ffd33d2d2956dfed6307960cf74c026385..3b67d5532de52d6ad848717e836e722d0ee29f4a 100644 --- a/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx @@ -90,7 +90,7 @@ class ApplicationMenu extends BaseMenu { // I used setTimout to create a smooth animation transition setTimeout(() => this.setState({ showSelect: true, - }), 500); + }), 100); } } diff --git a/bigbluebutton-html5/imports/ui/components/settings/submenus/styles.scss b/bigbluebutton-html5/imports/ui/components/settings/submenus/styles.scss index c30f8ddd5960524ec5baca2e7b648470b03de782..5ccab57d8cb0badbee1585016433098bd75ea2ba 100644 --- a/bigbluebutton-html5/imports/ui/components/settings/submenus/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/settings/submenus/styles.scss @@ -1,7 +1,7 @@ @import '/imports/ui/stylesheets/mixins/focus'; @import '/imports/ui/stylesheets/mixins/_indicators'; -@import '/imports/ui/stylesheets/variables/_all'; @import '/imports/ui/components/loading-screen/styles'; +@import "/imports/ui/stylesheets/variables/placeholders"; .title { color: var(--color-gray-dark); @@ -84,13 +84,9 @@ height: 1.75rem; padding: 1px; - &:hover { - @include highContrastOutline(); - } - + &:hover, &:focus { - @include highContrastOutline(); - outline-style: solid; + @extend %highContrastOutline; } } diff --git a/bigbluebutton-html5/imports/ui/components/shortcut-help/component.jsx b/bigbluebutton-html5/imports/ui/components/shortcut-help/component.jsx index 97a88ccd05d797e7c1635810f6c73eb0c836be7f..f916b8d7f89e8cc9086a25409595cddf77b01cfc 100644 --- a/bigbluebutton-html5/imports/ui/components/shortcut-help/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/shortcut-help/component.jsx @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import browser from 'browser-detect'; import Modal from '/imports/ui/components/modal/simple/component'; import _ from 'lodash'; @@ -179,11 +179,11 @@ const ShortcutHelpComponent = (props) => { }; ShortcutHelpComponent.defaultProps = { - intl: intlShape, + intl: {}, }; ShortcutHelpComponent.propTypes = { - intl: intlShape, + intl: PropTypes.object.isRequired, shortcuts: PropTypes.arrayOf(PropTypes.shape({ accesskey: PropTypes.string.isRequired, descId: PropTypes.string.isRequired, diff --git a/bigbluebutton-html5/imports/ui/components/shortcut-help/styles.scss b/bigbluebutton-html5/imports/ui/components/shortcut-help/styles.scss index 8da9085a7ae1d76be4df514e7c3762da5e9d7dec..227a000528f2d98eb4e4363c42b48ccee7e3b6b6 100644 --- a/bigbluebutton-html5/imports/ui/components/shortcut-help/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/shortcut-help/styles.scss @@ -1,5 +1,3 @@ -@import "/imports/ui/stylesheets/variables/_all"; - .shortcutTable, .keyCell, .descCell, diff --git a/bigbluebutton-html5/imports/ui/components/status-notifier/component.jsx b/bigbluebutton-html5/imports/ui/components/status-notifier/component.jsx index a337150da8595e686bd32bcf693680b0877ba430..78f822c1fbe5b18ebf25412fd42eed9c66ca32fa 100644 --- a/bigbluebutton-html5/imports/ui/components/status-notifier/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/status-notifier/component.jsx @@ -5,6 +5,7 @@ import { toast } from 'react-toastify'; import Icon from '/imports/ui/components/icon/component'; import Button from '/imports/ui/components/button/component'; import { ENTER } from '/imports/utils/keyCodes'; +import toastStyles from '/imports/ui/components/toast/styles'; import { styles } from './styles'; const messages = defineMessages({ @@ -71,7 +72,7 @@ class StatusNotifier extends Component { autoClose: false, closeOnClick: false, closeButton: false, - className: styles.raisedHandsToast, + className: toastStyles.actionToast, }); } break; @@ -161,7 +162,7 @@ class StatusNotifier extends Component { <div>{intl.formatMessage(messages.raisedHandsTitle)}</div> {formattedRaisedHands} </div> - <div className={styles.separator} /> + <div className={toastStyles.separator} /> <Button className={styles.clearBtn} label={intl.formatMessage(messages.lowerHandsLabel)} diff --git a/bigbluebutton-html5/imports/ui/components/status-notifier/styles.scss b/bigbluebutton-html5/imports/ui/components/status-notifier/styles.scss index 5f396dfcc917f95d91cba12c069a8ea94a8c8887..63e23aca1718cb90954aaab66979aca8ca77d550 100644 --- a/bigbluebutton-html5/imports/ui/components/status-notifier/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/status-notifier/styles.scss @@ -1,12 +1,8 @@ -@import "/imports/ui/stylesheets/variables/_all"; @import "/imports/ui/stylesheets/mixins/_indicators"; +@import "/imports/ui/stylesheets/variables/placeholders"; :root { - --iconWrapperSize: 40px; - --icon-offset: .6rem; - --innerToastWidth: 17rem; --toast-margin: .5rem; - --toast-icon-side: 40px; --avatar-side: 34px; --avatar-wrapper-offset: 14px; --avatar-inset: -7px; @@ -76,7 +72,7 @@ .avatarsExtra, .avatar { - @include highContrastOutline(); + @extend %highContrastOutline; width: var(--avatar-side); height: var(--avatar-side); color: var(--color-white); @@ -87,23 +83,6 @@ padding: 5px 0; } -.raisedHandsToast { - display: flex; - position: relative; - margin-bottom: var(--sm-padding-x); - padding: var(--md-padding-x); - border-radius: var(--border-radius); - box-shadow: 0 var(--border-size-small) 10px 0 rgba(0, 0, 0, 0.1), 0 var(--border-size) 15px 0 rgba(0, 0, 0, 0.05); - justify-content: space-between; - color: var(--color-text); - background-color: var(--color-white); - -webkit-animation-duration: 0.75s; - animation-duration: 0.75s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - max-width: var(--toast-max-width); -} - .avatar:hover, .avatar:focus { border: solid var(--border-size) var(--color-gray-lighter); @@ -118,8 +97,8 @@ > i { position: relative; color: var(--color-white); - top: var(--icon-offset); - left: var(--icon-offset); + top: var(--toast-margin); + left: var(--toast-margin); font-size: var(--font-size-xl); [dir="rtl"] & { diff --git a/bigbluebutton-html5/imports/ui/components/switch/styles.scss b/bigbluebutton-html5/imports/ui/components/switch/styles.scss index 331d5ede4153cc15e370cdf3153375817d76e3d7..bf6fe96e2cede5767c7a879e77a63081589d8568 100644 --- a/bigbluebutton-html5/imports/ui/components/switch/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/switch/styles.scss @@ -1,13 +1,14 @@ @import '/imports/ui/stylesheets/mixins/_indicators'; +@import "/imports/ui/stylesheets/variables/placeholders"; .switch { &:hover { - @include highContrastOutline(); + @extend %highContrastOutline; } &:focus, &:focus-within { - @include highContrastOutline(); + @extend %highContrastOutline; outline-style: solid; } } diff --git a/bigbluebutton-html5/imports/ui/components/toast/styles.scss b/bigbluebutton-html5/imports/ui/components/toast/styles.scss index a5ff87922320c9a71a9da1632519d2580ad3ef08..5d64cd55a3d7542d794106bf6acf11c4a073e888 100755 --- a/bigbluebutton-html5/imports/ui/components/toast/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/toast/styles.scss @@ -1,4 +1,4 @@ -@import "../../stylesheets/variables/_all"; +@import "../../stylesheets/variables/breakpoints"; :root { --toast-default-color: var(--color-white); @@ -144,21 +144,28 @@ } } -.toast { +.toast , +.actionToast { position: relative; margin-bottom: var(--sm-padding-x); - padding: var(--md-padding-x); + padding: var(--sm-padding-x); border-radius: var(--border-radius); box-shadow: 0 var(--border-size-small) 10px 0 rgba(0, 0, 0, 0.1), 0 var(--border-size) 15px 0 rgba(0, 0, 0, 0.05); display: flex; justify-content: space-between; - cursor: pointer; color: var(--color-text); - background-color: var(--background); + -webkit-animation-duration: 0.75s; animation-duration: 0.75s; + -webkit-animation-fill-mode: both; animation-fill-mode: both; max-width: var(--toast-max-width); + min-width: var(--toast-max-width); width: var(--toast-max-width); +} + +.toast { + cursor: pointer; + background-color: var(--background); &:hover, &:focus { @@ -166,6 +173,14 @@ } } +.actionToast { + background-color: var(--color-white); + + i.close { + left: none !important; + } +} + .body { margin: auto auto; flex: 1; diff --git a/bigbluebutton-html5/imports/ui/components/user-avatar/component.jsx b/bigbluebutton-html5/imports/ui/components/user-avatar/component.jsx index a9337f82cc3a5f616f8c4a53caa54416f39dd0c6..2cd8c75eea1cbefea439f955da74835f7a5b008e 100755 --- a/bigbluebutton-html5/imports/ui/components/user-avatar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-avatar/component.jsx @@ -14,6 +14,8 @@ const propTypes = { voice: PropTypes.bool, noVoice: PropTypes.bool, color: PropTypes.string, + emoji: PropTypes.bool, + avatar: PropTypes.string, className: PropTypes.string, }; @@ -26,6 +28,8 @@ const defaultProps = { voice: false, noVoice: false, color: '#000', + emoji: false, + avatar: '', className: null, }; @@ -38,6 +42,8 @@ const UserAvatar = ({ listenOnly, color, voice, + emoji, + avatar, noVoice, className, }) => ( @@ -60,14 +66,27 @@ const UserAvatar = ({ > <div className={cx({ - [styles.talking]: (talking && !muted), + [styles.talking]: (talking && !muted && avatar.length === 0), })} /> - - <div className={styles.content}> - {children} - </div> + {avatar.length !== 0 && !emoji + ? ( + <div className={styles.image}> + <img + className={cx(styles.img, { + [styles.circle]: !moderator, + [styles.square]: moderator, + })} + src={avatar} + /> + </div> + ) : ( + <div className={styles.content}> + {children} + </div> + ) + } </div> ); diff --git a/bigbluebutton-html5/imports/ui/components/user-avatar/styles.scss b/bigbluebutton-html5/imports/ui/components/user-avatar/styles.scss index b1a6ab48d4b898ba5cf177590bd485bb71b48899..99daf99adaabd60a11a5c5b1b436b99cc9ca0d8f 100755 --- a/bigbluebutton-html5/imports/ui/components/user-avatar/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/user-avatar/styles.scss @@ -1,5 +1,3 @@ -@import "/imports/ui/stylesheets/variables/palette"; -@import "/imports/ui/stylesheets/variables/general"; @import "/imports/ui/stylesheets/mixins/_indicators"; /* Variables @@ -10,13 +8,13 @@ --user-avatar-text: var(--color-white); --user-indicator-voice-bg: var(--color-success); --user-indicator-muted-bg: var(--color-danger); - --user-list-bg: var(--color-off-white); --user-color: currentColor; //picks the current color reference in the class } .avatar { position: relative; - padding-bottom: 2rem; + height: 2.25rem; + min-width: 2.25rem; border-radius: 50%; text-align: center; font-size: .85rem; @@ -169,6 +167,25 @@ @include indicatorStyles(); } +.image { + display: flex; + height: 2rem; + width: 2rem; + + .img { + object-fit: cover; + overflow: hidden; + } + + .circle { + border-radius: 50%; + } + + .square { + border-radius: 3px; + } +} + .content { color: var(--user-avatar-text); top: 50%; diff --git a/bigbluebutton-html5/imports/ui/components/user-info/component.jsx b/bigbluebutton-html5/imports/ui/components/user-info/component.jsx index 29bf4ad9d8b96d73a5493638e6325ec986096afb..2ddcd93c81dde75715f269ab53b7de47ba06a328 100644 --- a/bigbluebutton-html5/imports/ui/components/user-info/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-info/component.jsx @@ -1,5 +1,5 @@ import React, { Component } from 'react'; -import { defineMessages, intlShape } from 'react-intl'; +import { defineMessages } from 'react-intl'; import PropTypes from 'prop-types'; import Modal from '/imports/ui/components/modal/simple/component'; @@ -9,7 +9,7 @@ import Service from './service'; import { styles } from './styles'; const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, meetingId: PropTypes.string.isRequired, requesterUserId: PropTypes.string.isRequired, }; diff --git a/bigbluebutton-html5/imports/ui/components/user-info/styles.scss b/bigbluebutton-html5/imports/ui/components/user-info/styles.scss index 76960ff594cd7147442df257d7e2bdbac8021217..eeb728f50f7c0837f21cbf904b3681f3d45aa710 100644 --- a/bigbluebutton-html5/imports/ui/components/user-info/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/user-info/styles.scss @@ -1,5 +1,3 @@ -@import "/imports/ui/stylesheets/variables/_all"; - .keyCell, .valueCell, .userInfoTable { border: var(--border-size) solid var(--color-gray-lighter); } @@ -21,4 +19,3 @@ .keyCell, .valueCell { padding: var(--md-padding-x); } - diff --git a/bigbluebutton-html5/imports/ui/components/user-list/captions-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/captions-list-item/component.jsx index cb52c2b9fd4d1a1ee273c1dee02d0dd522551fd0..8b7d108dcdb5eaf1d5a08e42a4a85a36011c7b77 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/captions-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/captions-list-item/component.jsx @@ -2,11 +2,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import Icon from '/imports/ui/components/icon/component'; import { Session } from 'meteor/session'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import { styles } from '/imports/ui/components/user-list/user-list-content/styles'; const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, locale: PropTypes.shape({ locale: PropTypes.string.isRequired, name: PropTypes.string.isRequired, diff --git a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/chat-avatar/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/chat-avatar/component.jsx index f12f6104afa94c06ff1bcc78d225e3d240f5a64d..4d2e85177b9f3bb441bdc46f14c368ed03f031a5 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/chat-avatar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/chat-avatar/component.jsx @@ -12,11 +12,14 @@ const defaultProps = { }; const ChatAvatar = (props) => { - const { color, name, isModerator } = props; + const { + color, name, avatar, isModerator, + } = props; return ( <UserAvatar moderator={isModerator} + avatar={avatar} color={color} > {name.toLowerCase().slice(0, 2)} diff --git a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/chat-icon/styles.scss b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/chat-icon/styles.scss index a3a51f3f3a7e9a418a445a2b1c9a3eb7d93b54b8..c3a05cb6a99f1b3bbc88e57d3ee5f567dcae90b5 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/chat-icon/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/chat-icon/styles.scss @@ -1,15 +1,16 @@ -@import "/imports/ui/components/user-list/styles.scss"; @import "/imports/ui/stylesheets/mixins/_indicators"; +@import "/imports/ui/stylesheets/variables/placeholders"; .chatThumbnail { - @extend %flex-column; + display: flex; + flex-flow: column; color: var(--color-gray-light); justify-content: center; font-size: 175%; } .chatIcon { - @include highContrastOutline(); + @extend %highContrastOutline; outline-style: solid; flex: 0 0 2.2rem; } diff --git a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx index 6420327c623cf7eed982f22a47b8b131acaf6850..57f25f6ed8a3116e3f6a81169d7b825b8f45ba63 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx @@ -56,6 +56,7 @@ const handleClickToggleChat = (id) => { } else { Session.set('idChatOpen', ''); } + window.dispatchEvent(new Event('panelChanged')); }; const ChatListItem = (props) => { @@ -95,6 +96,7 @@ const ChatListItem = (props) => { <ChatAvatar isModerator={chat.isModerator} color={chat.color} + avatar={chat.avatar} name={chat.name.toLowerCase().slice(0, 2)} /> )} diff --git a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/styles.scss b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/styles.scss index 22f871624b4d56ff614afbaadd7f9214dcb06c67..9d57ed364c5b085ac3ee02fc1613dbfa51b54214 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/styles.scss @@ -1,7 +1,39 @@ -@import "../styles.scss"; -@import "/imports/ui/stylesheets/variables/_all"; -@import "/imports/ui/stylesheets/variables/typography"; @import "/imports/ui/stylesheets/mixins/_indicators"; +@import "/imports/ui/stylesheets/variables/placeholders"; + +%list-item { + display: flex; + flex-flow: row; + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + cursor: pointer; + + [dir="rtl"] & { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; + } + + &:first-child { + margin-top: 0; + } + + &:hover { + @extend %highContrastOutline; + background-color: var(--list-item-bg-hover); + } + + &:active, + &:focus { + @extend %highContrastOutline; + outline-style: solid; + background-color: var(--list-item-bg-hover); + box-shadow: inset 0 0 0 var(--border-size) var(--item-focus-border), inset 1px 0 0 1px var(--item-focus-border); + } +} .chatListItem { @extend %list-item; @@ -50,7 +82,7 @@ } .chatNameMain { - @extend %no-margin; + margin: 0; @extend %text-elipsis; font-weight: 400; font-size: var(--font-size-small); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/custom-logo/styles.scss b/bigbluebutton-html5/imports/ui/components/user-list/custom-logo/styles.scss index 13f042648f722b27c48c9da9c4e328ba382a65e5..a84124fff12dbf9a7633a7a93038f1a47d133b3f 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/custom-logo/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/user-list/custom-logo/styles.scss @@ -1,5 +1,3 @@ -@import "/imports/ui/stylesheets/variables/_all"; - .separator { height: 1px; background-color: var(--color-gray-lighter); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/service.js b/bigbluebutton-html5/imports/ui/components/user-list/service.js index 178deff67ddf2c3db946ac91250d9b223b6a38e3..5e7f54e3c7a733dde9a07ea4c021b3cd5eca02dc 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/service.js +++ b/bigbluebutton-html5/imports/ui/components/user-list/service.js @@ -256,6 +256,7 @@ const getActiveChats = (chatID) => { const activeChat = op; activeChat.unreadCounter = UnreadMessages.count(op.userId); activeChat.name = op.name; + activeChat.avatar = op.avatar; activeChat.isModerator = op.role === ROLE_MODERATOR; activeChat.lastActivity = idsWithTimeStamp[`${op.userId}`]; return activeChat; @@ -435,14 +436,30 @@ const muteAllExceptPresenter = (userId) => { makeCall('muteAllExceptPresenter', const changeRole = (userId, role) => { makeCall('changeRole', userId, role); }; -const roving = (event, changeState, elementsList, element) => { +const focusFirstDropDownItem = () => { + const dropdownContent = document.querySelector('div[data-test="dropdownContent"][style="visibility: visible;"]'); + if (!dropdownContent) return; + const list = dropdownContent.getElementsByTagName('li'); + list[0].focus(); +}; + +const roving = (...args) => { + const [ + event, + changeState, + elementsList, + element, + ] = args; + this.selectedElement = element; + const numberOfChilds = elementsList.childElementCount; const menuOpen = Session.get('dropdownOpen') || false; if (menuOpen) { const menuChildren = document.activeElement.getElementsByTagName('li'); if ([KEY_CODES.ESCAPE, KEY_CODES.ARROW_LEFT].includes(event.keyCode)) { + Session.set('dropdownOpen', false); document.activeElement.click(); } @@ -463,13 +480,15 @@ const roving = (event, changeState, elementsList, element) => { } if ([KEY_CODES.ESCAPE, KEY_CODES.TAB].includes(event.keyCode)) { + Session.set('dropdownOpen', false); document.activeElement.blur(); changeState(null); } if (event.keyCode === KEY_CODES.ARROW_DOWN) { const firstElement = elementsList.firstChild; - let elRef = element ? element.nextSibling : firstElement; + let elRef = element && numberOfChilds > 1 ? element.nextSibling : firstElement; + elRef = elRef || firstElement; changeState(elRef); } @@ -482,7 +501,10 @@ const roving = (event, changeState, elementsList, element) => { } if ([KEY_CODES.ARROW_RIGHT, KEY_CODES.SPACE, KEY_CODES.ENTER].includes(event.keyCode)) { - document.activeElement.firstChild.click(); + const tether = document.activeElement.firstChild; + const dropdownTrigger = tether.firstChild; + dropdownTrigger.click(); + focusFirstDropDownItem(); } }; @@ -503,17 +525,68 @@ const requestUserInformation = (userId) => { makeCall('requestUserInformation', userId); }; -export const getUserNamesLink = () => { +const sortUsersByFirstName = (a, b) => { + const aName = a.firstName.toLowerCase(); + const bName = b.firstName.toLowerCase(); + if (aName < bName) return -1; + if (aName > bName) return 1; + return 0; +}; + +const sortUsersByLastName = (a, b) => { + if (a.lastName && !b.lastName) return -1; + if (!a.lastName && b.lastName) return 1; + + const aName = a.lastName.toLowerCase(); + const bName = b.lastName.toLowerCase(); + + if (aName < bName) return -1; + if (aName > bName) return 1; + return 0; +}; + +const isUserPresenter = (userId) => { + const user = Users.findOne({ userId }, + { fields: { presenter: 1 } }); + return user ? user.presenter : false; +}; + +export const getUserNamesLink = (docTitle, fnSortedLabel, lnSortedLabel) => { const mimeType = 'text/plain'; - const userNamesObj = getUsers(); - const userNameListString = userNamesObj - .map(u => u.name) - .join('\r\n'); + const userNamesObj = getUsers() + .map((u) => { + const name = u.sortName.split(' '); + return ({ + firstName: name[0], + middleNames: name.length > 2 ? name.slice(1, name.length - 1) : null, + lastName: name.length > 1 ? name[name.length - 1] : null, + }); + }); + + const getUsernameString = (user) => { + const { firstName, middleNames, lastName } = user; + return `${firstName || ''} ${middleNames && middleNames.length > 0 ? middleNames.join(' ') : ''} ${lastName || ''}`; + }; + + const namesByFirstName = userNamesObj.sort(sortUsersByFirstName) + .map(u => getUsernameString(u)).join('\r\n'); + + const namesByLastName = userNamesObj.sort(sortUsersByLastName) + .map(u => getUsernameString(u)).join('\r\n'); + + const namesListsString = `${docTitle}\r\n\r\n${fnSortedLabel}\r\n${namesByFirstName} + \r\n\r\n${lnSortedLabel}\r\n${namesByLastName}`.replace(/ {2}/g, ' '); + const link = document.createElement('a'); - link.setAttribute('download', `save-users-list-${Date.now()}.txt`); + const meeting = Meetings.findOne({ meetingId: Auth.meetingID }, + { fields: { 'meetingProp.name': 1 } }); + const date = new Date(); + const time = `${date.getHours()}-${date.getMinutes()}`; + const dateString = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}_${time}`; + link.setAttribute('download', `bbb-${meeting.meetingProp.name}[users-list]_${dateString}.txt`); link.setAttribute( 'href', - `data: ${mimeType} ;charset=utf-16,${encodeURIComponent(userNameListString)}`, + `data: ${mimeType} ;charset=utf-16,${encodeURIComponent(namesListsString)}`, ); return link; }; @@ -544,4 +617,6 @@ export default { hasPrivateChatBetweenUsers, toggleUserLock, requestUserInformation, + focusFirstDropDownItem, + isUserPresenter, }; diff --git a/bigbluebutton-html5/imports/ui/components/user-list/styles.scss b/bigbluebutton-html5/imports/ui/components/user-list/styles.scss index 62212b02ce16ded93da2e349e56541c6e66eeaee..09be64dd4dc09c6de9032af8bab90de21469b150 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/user-list/styles.scss @@ -1,46 +1,6 @@ -@import "/imports/ui/stylesheets/variables/_all"; @import "/imports/ui/stylesheets/mixins/_scrollable"; @import "/imports/ui/stylesheets/mixins/_indicators"; - -/* Variables - * ========== - */ - -// TODO: would be better to have those variables scoped and not global -:root { - --unread-messages-bg: var(--color-danger); - --user-list-text: var(--color-gray); - --user-list-bg: var(--color-off-white); - - --user-thumbnail-border: var(--color-gray-light); - --user-thumbnail-text: var(--user-thumbnail-border); - - --voice-user-bg: var(--color-success); - --voice-user-text: var(--color-white); - - --moderator-text: var(--color-white); - --moderator-bg: var(--color-primary); - - --sub-name-color: var(--color-gray-light); - - --user-icons-color: var(--color-gray-light); - --user-icons-color-hover: var(--color-gray); - - --list-item-bg-hover: #dce4ed; - --item-focus-border: var(--color-blue-lighter); -} - -/* classes for extending - * ========== - */ -%flex-column { - display: flex; - flex-flow: column; -} - -%no-margin { - margin: 0; -} +@import "/imports/ui/stylesheets/variables/placeholders"; %list-item { display: flex; @@ -63,13 +23,13 @@ } &:hover { - @include highContrastOutline(); + @extend %highContrastOutline; background-color: var(--list-item-bg-hover); } &:active, &:focus { - @include highContrastOutline(); + @extend %highContrastOutline; outline-style: solid; background-color: var(--list-item-bg-hover); box-shadow: inset 0 0 0 var(--border-size) var(--item-focus-border), inset 1px 0 0 1px var(--item-focus-border); @@ -144,6 +104,7 @@ .userListColumn { @extend %flex-column; min-height: 0; + flex-grow: 1; } .enter, diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/breakout-room/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/breakout-room/component.jsx index 2371966dcebd2ea19ec83c19668894aafb260a61..ef853eea3a020a5df7e1b9fe892db46481903b51 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/breakout-room/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/breakout-room/component.jsx @@ -18,6 +18,7 @@ const toggleBreakoutPanel = () => { ? 'userlist' : 'breakoutroom', ); + window.dispatchEvent(new Event('panelChanged')); }; const BreakoutRoomItem = ({ diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/styles.scss b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/styles.scss index 3c24009616b4d975c8b125842361d220a403a360..c01bb76681860003abd6831dce8b8691ef38a746 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/styles.scss @@ -2,7 +2,7 @@ @import "/imports/ui/stylesheets/mixins/_scrollable"; @import "/imports/ui/stylesheets/mixins/focus"; @import "/imports/ui/stylesheets/mixins/_indicators"; -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/placeholders"; .content { @extend %flex-column; @@ -20,21 +20,52 @@ .scrollableList { @include elementFocus(var(--list-item-bg-hover)); @include scrollbox-vertical(var(--user-list-bg)); - @include highContrastOutline(); - &:focus-within, - &:focus { - outline-style: solid; + outline: none; + + &:hover { + @extend %highContrastOutline; } + &:focus, &:active { box-shadow: none; border-radius: none; } overflow-x: hidden; - outline-width: 1px !important; - outline-color: transparent !important; +} + +.virtulizedScrollableList { + @include elementFocus(var(--list-item-bg-hover)); + @include scrollbox-vertical(var(--user-list-bg)); + + + > div { + outline: none; + } + + + &:hover { + @extend %highContrastOutline; + } + + &:focus, + &:active { + box-shadow: none; + border-radius: none; + outline-style: none; + } + + flex-grow: 1; + flex-shrink: 1; + + margin: 0 0 1px var(--md-padding-y); + + [dir="rtl"] & { + margin: 0 var(--md-padding-y) 1px 0; + } + margin-left: 0; } .list { @@ -152,6 +183,11 @@ flex-shrink: 1; } +.scrollStyle { + @include scrollbox-vertical($bg-color: #f3f6f9); + outline: none; +} + .noteLock { font-weight: 200; font-size: var(--font-size-smaller); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx index a897a092bda2746d5b0a4c19e95bc671393af150..e918f217c22bbec6da9b9ac1ecde8eeef76d6525 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx @@ -1,11 +1,15 @@ import React, { Component } from 'react'; -import { TransitionGroup, CSSTransition } from 'react-transition-group'; import { defineMessages } from 'react-intl'; import PropTypes from 'prop-types'; -import cx from 'classnames'; import { styles } from '/imports/ui/components/user-list/user-list-content/styles'; import _ from 'lodash'; import { findDOMNode } from 'react-dom'; +import { + List, + AutoSizer, + CellMeasurer, + CellMeasurerCache, +} from 'react-virtualized'; import UserListItemContainer from './user-list-item/container'; import UserOptionsContainer from './user-options/container'; @@ -47,8 +51,15 @@ class UserParticipants extends Component { constructor() { super(); + this.cache = new CellMeasurerCache({ + fixedWidth: true, + keyMapper: () => 1, + }); + this.state = { selectedUser: null, + isOpen: false, + scrollArea: false, }; this.userRefs = []; @@ -56,7 +67,7 @@ class UserParticipants extends Component { this.getScrollContainerRef = this.getScrollContainerRef.bind(this); this.rove = this.rove.bind(this); this.changeState = this.changeState.bind(this); - this.getUsers = this.getUsers.bind(this); + this.rowRenderer = this.rowRenderer.bind(this); this.handleClickSelectedUser = this.handleClickSelectedUser.bind(this); } @@ -82,13 +93,20 @@ class UserParticipants extends Component { } componentDidUpdate(prevProps, prevState) { - const { selectedUser } = this.state; - - if (selectedUser === prevState.selectedUser) return; + const { compact } = this.props; + const { selectedUser, scrollArea } = this.state; + if (!compact && (!prevState.scrollArea && scrollArea)) { + scrollArea.addEventListener( + 'keydown', + this.rove, + ); + } if (selectedUser) { const { firstChild } = selectedUser; - if (firstChild) firstChild.focus(); + if (!firstChild.isEqualNode(document.activeElement)) { + firstChild.focus(); + } } } @@ -101,7 +119,12 @@ class UserParticipants extends Component { return this.refScrollContainer; } - getUsers() { + rowRenderer({ + index, + parent, + style, + key, + }) { const { compact, setEmojiStatus, @@ -110,21 +133,22 @@ class UserParticipants extends Component { currentUser, meetingIsBreakout, } = this.props; + const { scrollArea } = this.state; + const user = users[index]; - let index = -1; - - return users.map(u => ( - <CSSTransition - classNames={listTransition} - appear - enter - exit - timeout={0} - component="div" - className={cx(styles.participantsList)} - key={u.userId} + return ( + <CellMeasurer + key={key} + cache={this.cache} + columnIndex={0} + parent={parent} + rowIndex={index} > - <div ref={(node) => { this.userRefs[index += 1] = node; }}> + <span + style={style} + key={key} + id={`user-${user.userId}`} + > <UserListItemContainer {...{ compact, @@ -132,13 +156,14 @@ class UserParticipants extends Component { requestUserInformation, currentUser, meetingIsBreakout, + scrollArea, }} - user={u} + user={user} getScrollContainerRef={this.getScrollContainerRef} /> - </div> - </CSSTransition> - )); + </span> + </CellMeasurer> + ); } handleClickSelectedUser(event) { @@ -151,8 +176,9 @@ class UserParticipants extends Component { rove(event) { const { roving } = this.props; - const { selectedUser } = this.state; - const usersItemsRef = findDOMNode(this.refScrollItems); + const { selectedUser, scrollArea } = this.state; + const usersItemsRef = findDOMNode(scrollArea.firstChild); + roving(event, this.changeState, usersItemsRef, selectedUser); } @@ -169,6 +195,7 @@ class UserParticipants extends Component { currentUser, meetingIsBreakout, } = this.props; + const { isOpen, scrollArea } = this.state; return ( <div className={styles.userListColumn}> @@ -198,15 +225,40 @@ class UserParticipants extends Component { : <hr className={styles.separator} /> } <div - className={styles.scrollableList} + className={styles.virtulizedScrollableList} tabIndex={0} - ref={(ref) => { this.refScrollContainer = ref; }} + ref={(ref) => { + this.refScrollContainer = ref; + }} > - <div className={styles.list}> - <TransitionGroup ref={(ref) => { this.refScrollItems = ref; }}> - {this.getUsers()} - </TransitionGroup> - </div> + <span id="destination" /> + <AutoSizer> + {({ height, width }) => ( + <List + {...{ + isOpen, + users, + }} + ref={(ref) => { + if (ref !== null) { + this.listRef = ref; + } + + if (ref !== null && !scrollArea) { + this.setState({ scrollArea: findDOMNode(ref) }); + } + }} + rowHeight={this.cache.rowHeight} + rowRenderer={this.rowRenderer} + rowCount={users.length} + height={height - 1} + width={width - 1} + className={styles.scrollStyle} + overscanRowCount={30} + deferredMeasurementCache={this.cache} + /> + )} + </AutoSizer> </div> </div> ); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx index daf9550083152d0bc2071c9cca34bbbebdbd1391..873843591058d07f18279bdb09232cea0d8d9834 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx @@ -45,6 +45,10 @@ class UserListItem extends PureComponent { isMeteorConnected, isMe, voiceUser, + scrollArea, + notify, + raiseHandAudioAlert, + raiseHandPushAlert, } = this.props; const contents = ( @@ -76,6 +80,10 @@ class UserListItem extends PureComponent { isMeteorConnected, isMe, voiceUser, + scrollArea, + notify, + raiseHandAudioAlert, + raiseHandPushAlert, }} /> ); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx index 3fbbcf92988b812f8bc015673183bb1638e83939..a6ba7c34e5be4e001cf53dd70a79c22b0d11e9cf 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx @@ -4,12 +4,12 @@ import PropTypes from 'prop-types'; import { findDOMNode } from 'react-dom'; import UserAvatar from '/imports/ui/components/user-avatar/component'; import Icon from '/imports/ui/components/icon/component'; -import Dropdown from '/imports/ui/components/dropdown/component'; import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component'; import DropdownContent from '/imports/ui/components/dropdown/content/component'; import DropdownList from '/imports/ui/components/dropdown/list/component'; import DropdownListItem from '/imports/ui/components/dropdown/list/item/component'; import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component'; +import Dropdown from '/imports/ui/components/dropdown/component'; import lockContextContainer from '/imports/ui/components/lock-viewers/context/container'; import { withModalMounter } from '/imports/ui/components/modal/service'; import RemoveUserModal from '/imports/ui/components/modal/remove-user/component'; @@ -18,6 +18,7 @@ import { Session } from 'meteor/session'; import { styles } from './styles'; import UserName from '../user-name/component'; import UserIcons from '../user-icons/component'; +import Service from '../../../../service'; const messages = defineMessages({ presenter: { @@ -162,9 +163,7 @@ class UserDropdown extends PureComponent { this.renderUserAvatar = this.renderUserAvatar.bind(this); this.resetMenuState = this.resetMenuState.bind(this); this.makeDropdownItem = this.makeDropdownItem.bind(this); - } - componentWillMount() { this.title = _.uniqueId('dropdown-title-'); this.seperator = _.uniqueId('action-separator-'); } @@ -305,7 +304,10 @@ class UserDropdown extends PureComponent { { showNestedOptions: true, isActionsOpen: true, - }, Session.set('dropdownOpen', true), + }, () => { + Session.set('dropdownOpen', true); + Service.focusFirstDropDownItem(); + }, ), 'user', 'right_arrow', @@ -340,7 +342,7 @@ class UserDropdown extends PureComponent { )); } - if (allowedToMuteAudio && isMeteorConnected) { + if (allowedToMuteAudio && isMeteorConnected && !meetingIsBreakout) { actions.push(this.makeDropdownItem( 'mute', intl.formatMessage(messages.MuteUserAudioLabel), @@ -349,7 +351,7 @@ class UserDropdown extends PureComponent { )); } - if (allowedToUnmuteAudio && !userLocks.userMic && isMeteorConnected) { + if (allowedToUnmuteAudio && !userLocks.userMic && isMeteorConnected && !meetingIsBreakout) { actions.push(this.makeDropdownItem( 'unmute', intl.formatMessage(messages.UnmuteUserAudioLabel), @@ -468,26 +470,24 @@ class UserDropdown extends PureComponent { * Check if the dropdown is visible, if so, check if should be draw on top or bottom direction. */ checkDropdownDirection() { - const { getScrollContainerRef } = this.props; + const { scrollArea } = this.props; if (this.isDropdownActivedByUser()) { const dropdown = this.getDropdownMenuParent(); const dropdownTrigger = dropdown.children[0]; - const dropdownContent = dropdown.children[1]; - - const scrollContainer = getScrollContainerRef(); - const nextState = { dropdownVisible: true, }; + const dropdownContent = findDOMNode(this.dropdownContent); + const dropdownBoundaries = dropdownContent.getBoundingClientRect(); const isDropdownVisible = UserDropdown.checkIfDropdownIsVisible( - dropdownContent.offsetTop, - dropdownContent.offsetHeight, + dropdownBoundaries.y, + dropdownBoundaries.height, ); - if (!isDropdownVisible) { + if (!isDropdownVisible && scrollArea) { const { offsetTop, offsetHeight } = dropdownTrigger; - const offsetPageTop = (offsetTop + offsetHeight) - scrollContainer.scrollTop; + const offsetPageTop = (offsetTop + offsetHeight) - scrollArea.scrollTop; nextState.dropdownOffset = window.innerHeight - offsetPageTop; nextState.dropdownDirection = 'bottom'; @@ -538,6 +538,8 @@ class UserDropdown extends PureComponent { voice={voiceUser.isVoiceUser} noVoice={!voiceUser.isVoiceUser} color={user.color} + emoji={user.emoji !== 'none'} + avatar={user.avatar} > { userInBreakout @@ -593,6 +595,7 @@ class UserDropdown extends PureComponent { <div data-test={isMe(user.userId) ? 'userListItemCurrent' : 'userListItem'} className={!actions.length ? styles.userListItem : null} + style={{ direction: document.documentElement.dir }} > <div className={styles.userItemContents}> <div className={styles.userAvatar}> @@ -620,7 +623,7 @@ class UserDropdown extends PureComponent { ); if (!actions.length) return contents; - + const placement = `right ${dropdownDirection}`; return ( <Dropdown ref={(ref) => { this.dropdown = ref; }} @@ -632,6 +635,9 @@ class UserDropdown extends PureComponent { aria-haspopup="true" aria-live="assertive" aria-relevant="additions" + placement={placement} + getContent={dropdownContent => this.dropdownContent = dropdownContent} + tethered > <DropdownTrigger> {contents} @@ -639,10 +645,9 @@ class UserDropdown extends PureComponent { <DropdownContent style={{ visibility: dropdownVisible ? 'visible' : 'hidden', - [dropdownDirection]: `${dropdownOffset}px`, }} className={styles.dropdownContent} - placement={`right ${dropdownDirection}`} + placement={placement} > <DropdownList ref={(ref) => { this.list = ref; }} diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/styles.scss b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/styles.scss index 50e88bcdfa77a95641a5acb9e67fe64191328638..475b3ad06911ce6398649757312a93b7e4b5a1c4 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/styles.scss @@ -1,11 +1,40 @@ -@import "/imports/ui/components/user-list/styles.scss"; -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/breakpoints"; +@import "/imports/ui/stylesheets/variables/placeholders"; @import '/imports/ui/stylesheets/mixins/_indicators'; @import '/imports/ui/stylesheets/mixins/focus'; -@import "/imports/ui/components/modal/simple/styles"; -:root { - --description-margin: 3.5rem; +%list-item { + display: flex; + flex-flow: row; + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + cursor: pointer; + + [dir="rtl"] & { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; + } + + &:first-child { + margin-top: 0; + } + + &:hover { + @extend %highContrastOutline; + background-color: var(--list-item-bg-hover); + } + + &:active, + &:focus { + @extend %highContrastOutline; + outline-style: solid; + background-color: var(--list-item-bg-hover); + box-shadow: inset 0 0 0 var(--border-size) var(--item-focus-border), inset 1px 0 0 1px var(--item-focus-border); + } } /* Animations @@ -90,7 +119,7 @@ } .usertListItemWithMenu { - @include highContrastOutline(); + @extend %highContrastOutline; outline-style: solid; background-color: var(--list-item-bg-hover); box-shadow: inset 0 0 0 var(--border-size) var(--item-focus-border), inset 1px 0 0 1px var(--item-focus-border); @@ -117,7 +146,7 @@ flex-grow: 0; display: flex; flex-flow: row; - + margin: 0 0 1px var(--lg-padding-y); padding: var(--lg-padding-y) 0 var(--lg-padding-y) var(--lg-padding-y); [dir="rtl"] & { @@ -133,6 +162,12 @@ @extend %text-elipsis; cursor: default; min-width: 10vw; + @include mq($medium-only) { + min-width: 13vw; + } + @include mq($large-up) { + min-width: 8vw; + } max-width: 100%; overflow: visible; } diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/theteredDropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/theteredDropdown/component.jsx new file mode 100644 index 0000000000000000000000000000000000000000..542cb560af113c4e85b7b3521d30f261cf445404 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/theteredDropdown/component.jsx @@ -0,0 +1,279 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { findDOMNode } from 'react-dom'; +import cx from 'classnames'; +import { isMobile } from 'react-device-detect'; +import { defineMessages, injectIntl } from 'react-intl'; +import Button from '/imports/ui/components/button/component'; +import screenreaderTrap from 'makeup-screenreader-trap'; +import TetherComponent from 'react-tether'; +import { styles } from '/imports/ui/components/dropdown/styles'; + +import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component'; +import DropdownContent from '/imports/ui/components/dropdown/content/component'; + +const intlMessages = defineMessages({ + close: { + id: 'app.dropdown.close', + description: 'Close button label', + }, +}); + +const noop = () => { }; + +const propTypes = { + /** + * The dropdown needs a trigger and a content component as children + */ + children: (props, propName, componentName) => { + const children = props[propName]; + + if (!children || children.length < 2) { + return new Error(`Invalid prop \`${propName}\` supplied to` + + ` \`${componentName}\`. Validation failed.`); + } + + const trigger = children.find(x => x.type === DropdownTrigger); + const content = children.find(x => x.type === DropdownContent); + + if (!trigger) { + return new Error(`Invalid prop \`${propName}\` supplied to` + + ` \`${componentName}\`. Missing \`DropdownTrigger\`. Validation failed.`); + } + + if (!content) { + return new Error(`Invalid prop \`${propName}\` supplied to` + + ` \`${componentName}\`. Missing \`DropdownContent\`. Validation failed.`); + } + + return null; + }, + isOpen: PropTypes.bool, + keepOpen: PropTypes.bool, + onHide: PropTypes.func, + onShow: PropTypes.func, + autoFocus: PropTypes.bool, + intl: PropTypes.object.isRequired, +}; + +const defaultProps = { + children: null, + onShow: noop, + onHide: noop, + autoFocus: false, + isOpen: false, + keepOpen: null, +}; + +const attachments = { + 'right-bottom': 'bottom left', + 'right-top': 'bottom left', +}; + +const targetAttachments = { + 'right-bottom': 'bottom right', + 'right-top': 'top right', +}; + +class Dropdown extends Component { + constructor(props) { + super(props); + this.state = { isOpen: false }; + this.handleShow = this.handleShow.bind(this); + this.handleHide = this.handleHide.bind(this); + this.handleToggle = this.handleToggle.bind(this); + this.handleWindowClick = this.handleWindowClick.bind(this); + } + + componentWillUpdate(nextProps, nextState) { + return nextState.isOpen ? screenreaderTrap.trap(this.dropdown) : screenreaderTrap.untrap(); + } + + componentDidUpdate(prevProps, prevState) { + const { + onShow, + onHide, + keepOpen, + } = this.props; + const { isOpen } = this.state; + + if (isOpen && !prevState.isOpen) { onShow(); } + + if (!isOpen && prevState.isOpen) { onHide(); } + + if (prevProps.keepOpen && !keepOpen) { onHide(); } + } + + handleShow() { + Session.set('dropdownOpen', true); + const { + onShow, + } = this.props; + this.setState({ isOpen: true }, () => { + const { addEventListener } = window; + onShow(); + addEventListener('click', this.handleWindowClick, true); + }); + } + + handleHide() { + Session.set('dropdownOpen', false); + const { onHide } = this.props; + this.setState({ isOpen: false }, () => { + const { removeEventListener } = window; + onHide(); + removeEventListener('click', this.handleWindowClick, true); + }); + } + + handleWindowClick(event) { + const { keepOpen, onHide } = this.props; + const { isOpen } = this.state; + const triggerElement = findDOMNode(this.trigger); + const contentElement = findDOMNode(this.content); + if (!(triggerElement && contentElement)) return; + if (triggerElement && triggerElement.contains(event.target)) { + if (keepOpen) { + onHide(); + return; + } + if (isOpen) { + this.handleHide(); + return; + } + } + + if (keepOpen && isOpen && !contentElement.contains(event.target)) { + if (triggerElement) { + const { parentElement } = triggerElement; + if (parentElement) parentElement.focus(); + } + onHide(); + this.handleHide(); + return; + } + + if (keepOpen && triggerElement) { + const { parentElement } = triggerElement; + if (parentElement) parentElement.focus(); + } + + if (keepOpen !== null) return; + this.handleHide(); + } + + handleToggle() { + const { isOpen } = this.state; + return isOpen ? this.handleHide() : this.handleShow(); + } + + render() { + const { + children, + className, + intl, + keepOpen, + getContent, + placement, + ...otherProps + } = this.props; + + const { isOpen } = this.state; + + let trigger = children.find(x => x.type === DropdownTrigger); + let content = children.find(x => x.type === DropdownContent); + + trigger = React.cloneElement(trigger, { + ref: (ref) => { this.trigger = ref; }, + dropdownIsOpen: isOpen, + dropdownToggle: this.handleToggle, + dropdownShow: this.handleShow, + dropdownHide: this.handleHide, + }); + + content = React.cloneElement(content, { + ref: (ref) => { + getContent(ref); + this.content = ref; + }, + keepOpen, + 'aria-expanded': isOpen, + dropdownIsOpen: isOpen, + dropdownToggle: this.handleToggle, + dropdownShow: this.handleShow, + dropdownHide: this.handleHide, + }); + + const showCloseBtn = (isOpen && keepOpen) || (isOpen && keepOpen === null); + const placements = placement.replace(' ', '-'); + // workaround + const test = isMobile ? { + width: '100%', + height: '100%', + transform: 'translateY(0)', + } : { + width: '', + height: '', + transform: '', + }; + return ( + <div + className={cx(styles.dropdown, className)} + aria-live={otherProps['aria-live']} + aria-relevant={otherProps['aria-relevant']} + aria-haspopup={otherProps['aria-haspopup']} + aria-label={otherProps['aria-label']} + ref={(node) => { this.dropdown = node; }} + tabIndex={-1} + > + <TetherComponent + style={{ + zIndex: isOpen ? 15 : '', + ...test, + }} + attachment={ + isMobile ? 'middle bottom' + : attachments[placements] + } + targetAttachment={ + isMobile ? '' + : targetAttachments[placements] + } + + constraints={[ + { + to: 'scrollParent', + }, + ]} + + renderTarget={ref => ( + <span ref={ref}> + {trigger} + </span>)} + renderElement={ref => ( + <div + ref={ref} + > + {content} + {showCloseBtn + ? ( + <Button + className={styles.close} + label={intl.formatMessage(intlMessages.close)} + size="lg" + color="default" + onClick={this.handleHide} + /> + ) : null} + </div> + ) + } + /> + </div> + ); + } +} + +Dropdown.propTypes = propTypes; +Dropdown.defaultProps = defaultProps; +export default injectIntl(Dropdown); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-icons/styles.scss b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-icons/styles.scss index 129edb5739a8f686465a3eea8aefd5c1e640003b..667be763c579a5f7da44a8053a1aadfb17848565 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-icons/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-icons/styles.scss @@ -1,7 +1,6 @@ -@import "/imports/ui/components/user-list/styles.scss"; - .userIconsContainer { - @extend %flex-column; + display: flex; + flex-flow: column; text-align: center; flex-basis: 1rem; justify-content: center; diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-name/styles.scss b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-name/styles.scss index 7b03ad89ce08136da02a872dbe047d30ab0c53a0..6975b69113c6d39d1f29d32a66320ee5deb44241 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-name/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-name/styles.scss @@ -1,7 +1,6 @@ -@import "/imports/ui/components/user-list/styles.scss"; - .userName { - @extend %flex-column; + display: flex; + flex-flow: column; min-width: 0; flex-grow: 1; margin: 0 0 0 var(--sm-padding-x); @@ -14,7 +13,7 @@ } .userNameMain { - @extend %no-margin; + margin: 0; font-size: 0.9rem; overflow: hidden; text-overflow: ellipsis; @@ -36,7 +35,7 @@ } .userNameSub { - @extend %no-margin; + margin: 0; font-size: 0.75rem; font-weight: 200; color: var(--color-gray); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx index d2580f7372090e96ba2f2714626fe17d9d7aa51a..0cf23728298e01d8780c27784e30353024bf6fc7 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx @@ -112,6 +112,18 @@ const intlMessages = defineMessages({ id: 'app.actionsBar.actionsDropdown.captionsDesc', description: 'Captions menu toggle description', }, + savedNamesListTitle: { + id: 'app.userList.userOptions.savedNames.title', + description: '', + }, + sortedFirstNameHeading: { + id: 'app.userList.userOptions.sortedFirstName.heading', + description: '', + }, + sortedLastNameHeading: { + id: 'app.userList.userOptions.sortedLastName.heading', + description: '', + }, }); class UserOptions extends PureComponent { @@ -142,7 +154,21 @@ class UserOptions extends PureComponent { } onSaveUserNames() { - getUserNamesLink().dispatchEvent(new MouseEvent('click', + const { intl, meetingName } = this.props; + const date = new Date(); + getUserNamesLink( + intl.formatMessage(intlMessages.savedNamesListTitle, + { + 0: meetingName, + 1: `${date.toLocaleDateString( + document.documentElement.lang, + )}:${date.toLocaleTimeString( + document.documentElement.lang, + )}`, + }), + intl.formatMessage(intlMessages.sortedFirstNameHeading), + intl.formatMessage(intlMessages.sortedLastNameHeading), + ).dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })); } @@ -224,7 +250,7 @@ class UserOptions extends PureComponent { onClick={toggleStatus} />) : null ), - (isMeteorConnected ? ( + (!meetingIsBreakout && isMeteorConnected ? ( <DropdownListItem key={this.muteAllId} icon={isMeetingMuted ? 'unmute' : 'mute'} @@ -233,7 +259,7 @@ class UserOptions extends PureComponent { onClick={toggleMuteAllUsers} />) : null ), - (!isMeetingMuted && isMeteorConnected ? ( + (!meetingIsBreakout && !isMeetingMuted && isMeteorConnected ? ( <DropdownListItem key={this.muteId} icon="mute" diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx index a1125ea6f2a13e22e0abb9ff83b477dc357363b0..f24b76e177f05099e697b713e0f6a94bad863d2c 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx @@ -5,14 +5,14 @@ import Meetings from '/imports/api/meetings'; import ActionsBarService from '/imports/ui/components/actions-bar/service'; import UserListService from '/imports/ui/components/user-list/service'; import logger from '/imports/startup/client/logger'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import { notify } from '/imports/ui/services/notification'; import UserOptions from './component'; const propTypes = { users: PropTypes.arrayOf(Object).isRequired, setEmojiStatus: PropTypes.func.isRequired, - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, }; const intlMessages = defineMessages({ @@ -48,6 +48,13 @@ const UserOptionsContainer = withTracker((props) => { return muteOnStart; }; + const getMeetingName = () => { + const { meetingProp } = Meetings.findOne({ meetingId: Auth.meetingID }, + { fields: { 'meetingProp.name': 1 } }); + const { name } = meetingProp; + return name; + }; + return { toggleMuteAllUsers: () => { UserListService.muteAllUsers(Auth.userID); @@ -78,6 +85,7 @@ const UserOptionsContainer = withTracker((props) => { isBreakoutRecordable: ActionsBarService.isBreakoutRecordable(), users: ActionsBarService.users(), isMeteorConnected: Meteor.status().connected, + meetingName: getMeetingName(), }; })(UserOptions); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/styles.scss b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/styles.scss index 2f7a0dc89bacdbf9e24365c057bb151cd6c65362..4e00dc6057ecfd1af93649df8be19e8ca28e5e80 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/styles.scss @@ -1,4 +1,4 @@ -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/breakpoints"; :root { --user-manage-menu-top : -0.5rem; diff --git a/bigbluebutton-html5/imports/ui/components/video-preview/component.jsx b/bigbluebutton-html5/imports/ui/components/video-preview/component.jsx index 9fd9e78f0c145765a4d979554af848571d8c5ae5..4237f72f5cb1e30a7653c48ee34c2fa6c4d94d95 100755 --- a/bigbluebutton-html5/imports/ui/components/video-preview/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-preview/component.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { - defineMessages, injectIntl, intlShape, FormattedMessage, + defineMessages, injectIntl, FormattedMessage, } from 'react-intl'; import Button from '/imports/ui/components/button/component'; // import { notify } from '/imports/ui/services/notification'; @@ -21,7 +21,7 @@ const VIEW_STATES = { }; const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, closeModal: PropTypes.func.isRequired, startSharing: PropTypes.func.isRequired, stopSharing: PropTypes.func.isRequired, @@ -62,6 +62,22 @@ const intlMessages = defineMessages({ id: 'app.videoPreview.profileLabel', description: 'Quality dropdown label', }, + low: { + id: 'app.videoPreview.quality.low', + description: 'Low quality option label', + }, + medium: { + id: 'app.videoPreview.quality.medium', + description: 'Medium quality option label', + }, + high: { + id: 'app.videoPreview.quality.high', + description: 'High quality option label', + }, + hd: { + id: 'app.videoPreview.quality.hd', + description: 'High definition option label', + }, cancelLabel: { id: 'app.videoPreview.cancelLabel', description: 'Cancel button label', @@ -395,7 +411,7 @@ class VideoPreview extends Component { displayInitialPreview(deviceId) { const { changeWebcam } = this.props; - const availableProfiles = CAMERA_PROFILES; + const availableProfiles = CAMERA_PROFILES.filter(p => !p.hidden); this.setState({ webcamDeviceId: deviceId, @@ -540,11 +556,16 @@ class VideoPreview extends Component { onChange={this.handleSelectProfile} disabled={skipVideoPreview} > - {availableProfiles.map(profile => ( + {availableProfiles.map(profile => { + const label = intlMessages[`${profile.id}`] + ? intl.formatMessage(intlMessages[`${profile.id}`]) + : profile.name; + + return ( <option key={profile.id} value={profile.id}> - {profile.name} + {`${label} ${profile.id === 'hd' ? '' : intl.formatMessage(intlMessages.qualityLabel).toLowerCase()}`} </option> - ))} + )})} </select> ) : ( @@ -602,7 +623,7 @@ class VideoPreview extends Component { : ( <video id="preview" - data-test="videoPreview" + data-test={this.mirrorOwnWebcam ? 'mirroredVideoPreview' : 'videoPreview'} className={cx({ [styles.preview]: true, [styles.mirroredVideo]: this.mirrorOwnWebcam, diff --git a/bigbluebutton-html5/imports/ui/components/video-preview/styles.scss b/bigbluebutton-html5/imports/ui/components/video-preview/styles.scss index e4ec3e41c3ef1fa7c32847f9b3f0800d0b4c1295..ba94327c83d4135472628b588dda4d77b7e59c71 100755 --- a/bigbluebutton-html5/imports/ui/components/video-preview/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/video-preview/styles.scss @@ -1,5 +1,4 @@ -@import "/imports/ui/stylesheets/variables/_all"; -@import '/imports/ui/stylesheets/mixins/focus'; +@import "/imports/ui/stylesheets/variables/breakpoints"; @import "/imports/ui/components/modal/simple/styles"; .warning { diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx index 910f89894aeedad10b7c019a322d9685cce3c13f..02c4533a6f155303ce432775aa94b0a2d95f7ea4 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx @@ -1,9 +1,10 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import ReconnectingWebSocket from 'reconnecting-websocket'; -import VideoService from './service'; -import VideoList from './video-list/component'; import { defineMessages, injectIntl } from 'react-intl'; +import _ from 'lodash'; +import VideoService from './service'; +import VideoListContainer from './video-list/container'; import { fetchWebRTCMappedStunTurnServers, getMappedFallbackStun, @@ -11,8 +12,15 @@ import { import { tryGenerateIceCandidates } from '/imports/utils/safari-webrtc'; import logger from '/imports/startup/client/logger'; -const CAMERA_SHARE_FAILED_WAIT_TIME = 15000; -const MAX_CAMERA_SHARE_FAILED_WAIT_TIME = 60000; +// Default values and default empty object to be backwards compat with 2.2. +// FIXME Remove hardcoded defaults 2.3. +const WS_CONN_TIMEOUT = Meteor.settings.public.kurento.wsConnectionTimeout || 4000; + +const { + baseTimeout: CAMERA_SHARE_FAILED_WAIT_TIME = 15000, + maxTimeout: MAX_CAMERA_SHARE_FAILED_WAIT_TIME = 60000, +} = Meteor.settings.public.kurento.cameraTimeouts || {}; +const CAMERA_QUALITY_THRESHOLDS_ENABLED = Meteor.settings.public.kurento.cameraQualityThresholds.enabled; const PING_INTERVAL = 15000; const intlClientErrors = defineMessages({ @@ -74,9 +82,15 @@ const propTypes = { intl: PropTypes.objectOf(Object).isRequired, isUserLocked: PropTypes.bool.isRequired, swapLayout: PropTypes.bool.isRequired, + currentVideoPageIndex: PropTypes.number.isRequired, + totalNumberOfStreams: PropTypes.number.isRequired, }; class VideoProvider extends Component { + static onBeforeUnload() { + VideoService.onBeforeUnload(); + } + constructor(props) { super(props); @@ -87,7 +101,11 @@ class VideoProvider extends Component { this.info = VideoService.getInfo(); // Set a valid bbb-webrtc-sfu application server socket in the settings - this.ws = new ReconnectingWebSocket(VideoService.getAuthenticatedURL()); + this.ws = new ReconnectingWebSocket( + VideoService.getAuthenticatedURL(), + [], + { connectionTimeout: WS_CONN_TIMEOUT }, + ); this.wsQueue = []; this.restartTimeout = {}; @@ -101,9 +119,12 @@ class VideoProvider extends Component { this.onWsClose = this.onWsClose.bind(this); this.onWsMessage = this.onWsMessage.bind(this); - this.onBeforeUnload = this.onBeforeUnload.bind(this); - this.updateStreams = this.updateStreams.bind(this); + this.debouncedConnectStreams = _.debounce( + this.connectStreams, + VideoService.getPageChangeDebounceTime(), + { leading: false, trailing: true }, + ); } componentDidMount() { @@ -115,13 +136,17 @@ class VideoProvider extends Component { this.ws.onmessage = this.onWsMessage; - window.addEventListener('beforeunload', this.onBeforeUnload); + window.addEventListener('beforeunload', VideoProvider.onBeforeUnload); } componentDidUpdate(prevProps) { - const { isUserLocked, streams } = this.props; + const { isUserLocked, streams, currentVideoPageIndex } = this.props; - this.updateStreams(streams); + // Only debounce when page changes to avoid unecessary debouncing + const shouldDebounce = VideoService.isPaginationEnabled() + && prevProps.currentVideoPageIndex !== currentVideoPageIndex; + + this.updateStreams(streams, shouldDebounce); if (!prevProps.isUserLocked && isUserLocked) VideoService.lockUser(); } @@ -134,7 +159,7 @@ class VideoProvider extends Component { window.removeEventListener('online', this.openWs); window.removeEventListener('offline', this.onWsClose); - window.removeEventListener('beforeunload', this.onBeforeUnload); + window.removeEventListener('beforeunload', VideoProvider.onBeforeUnload); VideoService.exitVideo(); @@ -205,26 +230,61 @@ class VideoProvider extends Component { this.setState({ socketOpen: true }); } - onBeforeUnload() { - VideoService.onBeforeUnload(); + updateThreshold(numberOfPublishers) { + const { threshold, profile } = VideoService.getThreshold(numberOfPublishers); + if (profile) { + const publishers = Object.values(this.webRtcPeers) + .filter(peer => peer.isPublisher) + .forEach((peer) => { + // 0 means no threshold in place. Reapply original one if needed + const profileToApply = (threshold === 0) ? peer.originalProfileId : profile; + VideoService.applyCameraProfile(peer, profileToApply); + }); + } } - updateStreams(streams) { + getStreamsToConnectAndDisconnect(streams) { const streamsCameraIds = streams.map(s => s.cameraId); const streamsConnected = Object.keys(this.webRtcPeers); - const streamsToConnect = streamsCameraIds.filter(cameraId => !streamsConnected.includes(cameraId)); + const streamsToConnect = streamsCameraIds.filter( + cameraId => !streamsConnected.includes(cameraId), + ); - const streamsToDisconnect = streamsConnected.filter(cameraId => !streamsCameraIds.includes(cameraId)); + const streamsToDisconnect = streamsConnected.filter( + cameraId => !streamsCameraIds.includes(cameraId), + ); + + return [streamsToConnect, streamsToDisconnect]; + } + connectStreams(streamsToConnect) { streamsToConnect.forEach((cameraId) => { const isLocal = VideoService.isLocalStream(cameraId); this.createWebRTCPeer(cameraId, isLocal); }); + } + disconnectStreams(streamsToDisconnect) { streamsToDisconnect.forEach(cameraId => this.stopWebRTCPeer(cameraId)); } + updateStreams(streams, shouldDebounce = false) { + const [streamsToConnect, streamsToDisconnect] = this.getStreamsToConnectAndDisconnect(streams); + + if (shouldDebounce) { + this.debouncedConnectStreams(streamsToConnect); + } else { + this.connectStreams(streamsToConnect); + } + + this.disconnectStreams(streamsToDisconnect); + + if (CAMERA_QUALITY_THRESHOLDS_ENABLED) { + this.updateThreshold(this.props.totalNumberOfStreams); + } + } + ping() { const message = { id: 'ping' }; this.sendMessage(message); @@ -461,6 +521,10 @@ class VideoProvider extends Component { peer.started = false; peer.attached = false; peer.didSDPAnswered = false; + peer.isPublisher = isLocal; + peer.originalProfileId = profileId; + peer.currentProfileId = profileId; + if (peer.inboundIceQueue == null) { peer.inboundIceQueue = []; } @@ -485,6 +549,7 @@ class VideoProvider extends Component { userId: this.info.userId, userName: this.info.userName, bitrate, + record: VideoService.getRecord(), }; logger.info({ @@ -505,7 +570,8 @@ class VideoProvider extends Component { const peer = this.webRtcPeers[cameraId]; if (peer && peer.peerConnection) { const conn = peer.peerConnection; - conn.oniceconnectionstatechange = this._getOnIceConnectionStateChangeCallback(cameraId, isLocal); + conn.oniceconnectionstatechange = this + ._getOnIceConnectionStateChangeCallback(cameraId, isLocal); VideoService.monitor(conn); } } @@ -814,18 +880,14 @@ class VideoProvider extends Component { } render() { - const { swapLayout } = this.props; - const { socketOpen } = this.state; - if (!socketOpen) return null; + const { swapLayout, currentVideoPageIndex, streams } = this.props; - const { - streams, - } = this.props; return ( - <VideoList + <VideoListContainer streams={streams} onMount={this.createVideoTag} swapLayout={swapLayout} + currentVideoPageIndex={currentVideoPageIndex} /> ); } diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/container.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/container.jsx index 6e3e491010fe05d77f308e49b388d1085f32c2bb..338eb21bb1b9df7d212fe45cff4ed3a3d923bf8f 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/container.jsx @@ -2,14 +2,29 @@ import React from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import VideoProvider from './component'; import VideoService from './service'; +import { withLayoutContext } from '/imports/ui/components/layout/context'; const VideoProviderContainer = ({ children, ...props }) => { const { streams } = props; return (!streams.length ? null : <VideoProvider {...props}>{children}</VideoProvider>); }; -export default withTracker(props => ({ - swapLayout: props.swapLayout, - streams: VideoService.getVideoStreams(), - isUserLocked: VideoService.isUserLocked(), -}))(VideoProviderContainer); +export default withTracker(props => { + // getVideoStreams returns a dictionary consisting of: + // { + // streams: array of mapped streams + // totalNumberOfStreams: total number of shared streams in the server + // } + const { + streams, + totalNumberOfStreams + } = VideoService.getVideoStreams(); + + return { + swapLayout: props.swapLayout, + streams, + totalNumberOfStreams, + isUserLocked: VideoService.isUserLocked(), + currentVideoPageIndex: VideoService.getCurrentVideoPageIndex(), + }; +})( withLayoutContext(VideoProviderContainer)); diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/many-users-notify/styles.scss b/bigbluebutton-html5/imports/ui/components/video-provider/many-users-notify/styles.scss index ceb51079142a7726124614f2499fe522849be7af..99e590655f89b50a02857d96dc33da8e14ad9f84 100644 --- a/bigbluebutton-html5/imports/ui/components/video-provider/many-users-notify/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/video-provider/many-users-notify/styles.scss @@ -1,5 +1,4 @@ @import '/imports/ui/components/breakout-room/styles'; -@import "/imports/ui/stylesheets/variables/_all"; .buttonWrapper, .button, diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/service.js b/bigbluebutton-html5/imports/ui/components/video-provider/service.js index c25a1cb3ddccb4b02b0c1e724cac57f1ba68f3fe..adc0a46804f1bea06327f632ac523c52f6404d79 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/service.js +++ b/bigbluebutton-html5/imports/ui/components/video-provider/service.js @@ -12,6 +12,7 @@ import { monitorVideoConnection } from '/imports/utils/stats'; import browser from 'browser-detect'; import getFromUserSettings from '/imports/ui/services/users-settings'; import logger from '/imports/startup/client/logger'; +import _ from 'lodash'; const CAMERA_PROFILES = Meteor.settings.public.kurento.cameraProfiles; const MULTIPLE_CAMERAS = Meteor.settings.public.app.enableMultipleCameras; @@ -22,23 +23,58 @@ const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator; const ROLE_VIEWER = Meteor.settings.public.user.role_viewer; const ENABLE_NETWORK_MONITORING = Meteor.settings.public.networkMonitoring.enableNetworkMonitoring; const MIRROR_WEBCAM = Meteor.settings.public.app.mirrorOwnWebcam; +const CAMERA_QUALITY_THRESHOLDS = Meteor.settings.public.kurento.cameraQualityThresholds.thresholds || []; +const { + enabled: PAGINATION_ENABLED, + pageChangeDebounceTime: PAGE_CHANGE_DEBOUNCE_TIME, + desktopPageSizes: DESKTOP_PAGE_SIZES, + mobilePageSizes: MOBILE_PAGE_SIZES, +} = Meteor.settings.public.kurento.pagination; const TOKEN = '_'; class VideoService { + // Paginated streams: sort with following priority: local -> presenter -> alphabetic + static sortPaginatedStreams(s1, s2) { + if (UserListService.isUserPresenter(s1.userId) && !UserListService.isUserPresenter(s2.userId)) { + return -1; + } else if (UserListService.isUserPresenter(s2.userId) && !UserListService.isUserPresenter(s1.userId)) { + return 1; + } else { + return UserListService.sortUsersByName(s1, s2); + } + } + + // Full mesh: sort with the following priority: local -> alphabetic + static sortMeshStreams(s1, s2) { + if (s1.userId === Auth.userID && s2.userId !== Auth.userID) { + return -1; + } else if (s2.userId === Auth.userID && s1.userId !== Auth.userID) { + return 1; + } else { + return UserListService.sortUsersByName(s1, s2); + } + } + constructor() { this.defineProperties({ isConnecting: false, isConnected: false, + currentVideoPageIndex: 0, + numberOfPages: 0, }); this.skipVideoPreview = null; this.userParameterProfile = null; const BROWSER_RESULTS = browser(); this.isMobile = BROWSER_RESULTS.mobile || BROWSER_RESULTS.os.includes('Android'); this.isSafari = BROWSER_RESULTS.name === 'safari'; + this.pageChangeLocked = false; this.numberOfDevices = 0; + this.record = null; + this.hackRecordViewer = null; + // If the page isn't served over HTTPS there won't be mediaDevices if (navigator.mediaDevices) { this.updateNumberOfDevices = this.updateNumberOfDevices.bind(this); @@ -162,6 +198,103 @@ class VideoService { return Auth.authenticateURL(SFU_URL); } + isPaginationEnabled () { + return PAGINATION_ENABLED && (this.getMyPageSize() > 0); + } + + setNumberOfPages (numberOfPublishers, numberOfSubscribers, pageSize) { + // Page size 0 means no pagination, return itself + if (pageSize === 0) return 0; + + // Page size refers only to the number of subscribers. Publishers are always + // shown, hence not accounted for + const nofPages = Math.ceil((numberOfSubscribers || numberOfPublishers) / pageSize); + + if (nofPages !== this.numberOfPages) { + this.numberOfPages = nofPages; + // Check if we have to page back on the current video page index due to a + // page ceasing to exist + if ((this.currentVideoPageIndex + 1) > this.numberOfPages) { + this.getPreviousVideoPage(); + } + } + + return this.numberOfPages; + } + + getNumberOfPages () { + return this.numberOfPages; + } + + setCurrentVideoPageIndex (newVideoPageIndex) { + if (this.currentVideoPageIndex !== newVideoPageIndex) { + this.currentVideoPageIndex = newVideoPageIndex; + } + } + + getCurrentVideoPageIndex () { + return this.currentVideoPageIndex; + } + + calculateNextPage () { + if (this.numberOfPages === 0) { + return 0; + } + + return ((this.currentVideoPageIndex + 1) % this.numberOfPages + this.numberOfPages) % this.numberOfPages; + } + + calculatePreviousPage () { + if (this.numberOfPages === 0) { + return 0; + } + + return ((this.currentVideoPageIndex - 1) % this.numberOfPages + this.numberOfPages) % this.numberOfPages; + } + + getNextVideoPage() { + const nextPage = this.calculateNextPage(); + this.setCurrentVideoPageIndex(nextPage); + + return this.currentVideoPageIndex; + } + + getPreviousVideoPage() { + const previousPage = this.calculatePreviousPage(); + this.setCurrentVideoPageIndex(previousPage); + + return this.currentVideoPageIndex; + } + + getMyPageSize () { + const myRole = this.getMyRole(); + const pageSizes = !this.isMobile ? DESKTOP_PAGE_SIZES : MOBILE_PAGE_SIZES; + + switch (myRole) { + case ROLE_MODERATOR: + return pageSizes.moderator; + case ROLE_VIEWER: + default: + return pageSizes.viewer + } + } + + getVideoPage (streams, pageSize) { + // Publishers are taken into account for the page size calculations. They + // also appear on every page. + const [mine, others] = _.partition(streams, (vs => { return Auth.userID === vs.userId; })); + + // Recalculate total number of pages + this.setNumberOfPages(mine.length, others.length, pageSize); + const chunkIndex = this.currentVideoPageIndex * pageSize; + const paginatedStreams = others + .sort(VideoService.sortPaginatedStreams) + .slice(chunkIndex, (chunkIndex + pageSize)) || []; + const streamsOnPage = [...mine, ...paginatedStreams]; + + return streamsOnPage; + } + getVideoStreams() { let streams = VideoStreams.find( { meetingId: Auth.meetingID }, @@ -172,18 +305,32 @@ class VideoService { }, ).fetch(); - const hideUsers = this.hideUserList(); const moderatorOnly = this.webcamsOnlyForModerator(); - if (hideUsers || moderatorOnly) streams = this.filterModeratorOnly(streams); + if (moderatorOnly) streams = this.filterModeratorOnly(streams); const connectingStream = this.getConnectingStream(streams); if (connectingStream) streams.push(connectingStream); - return streams.map(vs => ({ + const mappedStreams = streams.map(vs => ({ cameraId: vs.stream, userId: vs.userId, name: vs.name, - })).sort(UserListService.sortUsersByName); + })); + + const pageSize = this.getMyPageSize(); + + // Pagination is either explictly disabled or pagination is set to 0 (which + // is equivalent to disabling it), so return the mapped streams as they are + // which produces the original non paginated behaviour + if (!PAGINATION_ENABLED || pageSize === 0) { + return { + streams: mappedStreams.sort(VideoService.sortMeshStreams), + totalNumberOfStreams: mappedStreams.length + }; + } + + const paginatedStreams = this.getVideoPage(mappedStreams, pageSize); + return { streams: paginatedStreams, totalNumberOfStreams: mappedStreams.length }; } getConnectingStream(streams) { @@ -227,9 +374,38 @@ class VideoService { return streams.find(s => s.stream === stream); } + getMyRole () { + return Users.findOne({ userId: Auth.userID }, + { fields: { role: 1 } })?.role; + } + + getRecord() { + if (this.record === null) { + this.record = getFromUserSettings('bbb_record_video', true); + } + + // TODO: Remove this + // This is a hack to handle a missing piece at the backend of a particular deploy. + // If, at the time the video is shared, the user has a viewer role and + // meta_hack-record-viewer-video is 'false' this user won't have this video + // stream recorded. + if (this.hackRecordViewer === null) { + const prop = Meetings.findOne( + { meetingId: Auth.meetingID }, + { fields: { 'metadataProp': 1 } }, + ).metadataProp; + + const value = prop.metadata ? prop.metadata['hack-record-viewer-video'] : null; + this.hackRecordViewer = value ? value.toLowerCase() === 'true' : true; + } + + const hackRecord = this.getMyRole() === ROLE_MODERATOR || this.hackRecordViewer; + + return this.record && hackRecord; + } + filterModeratorOnly(streams) { - const me = Users.findOne({ userId: Auth.userID }); - const amIViewer = me.role === ROLE_VIEWER; + const amIViewer = this.getMyRole() === ROLE_VIEWER; if (amIViewer) { const moderators = Users.find( @@ -245,7 +421,7 @@ class VideoService { const { userId } = stream; const isModerator = moderators.includes(userId); - const isMe = me.userId === userId; + const isMe = Auth.userID === userId; if (isModerator || isMe) result.push(stream); @@ -264,13 +440,7 @@ class VideoService { webcamsOnlyForModerator() { const m = Meetings.findOne({ meetingId: Auth.meetingID }, { fields: { 'usersProp.webcamsOnlyForModerator': 1 } }); - return m.usersProp ? m.usersProp.webcamsOnlyForModerator : false; - } - - hideUserList() { - const m = Meetings.findOne({ meetingId: Auth.meetingID }, - { fields: { 'lockSettingsProps.hideUserList': 1 } }); - return m.lockSettingsProps ? m.lockSettingsProps.hideUserList : false; + return m?.usersProp ? m.usersProp.webcamsOnlyForModerator : false; } getInfo() { @@ -372,10 +542,17 @@ class VideoService { this.exitVideo(); } - isDisabled() { + disableReason() { const { viewParticipantsWebcams } = Settings.dataSaving; - - return this.isUserLocked() || this.isConnecting || !viewParticipantsWebcams; + const locks = { + videoLocked: this.isUserLocked(), + videoConnecting: this.isConnecting, + dataSaving: !viewParticipantsWebcams, + meteorDisconnected: !Meteor.status().connected + }; + const locksKeys = Object.keys(locks); + const disableReason = locksKeys.filter( i => locks[i]).shift(); + return disableReason ? disableReason : false; } getRole(isLocal) { @@ -415,6 +592,140 @@ class VideoService { monitor(conn) { if (ENABLE_NETWORK_MONITORING) monitorVideoConnection(conn); } + + // to be used soon (Paulo) + amIModerator() { + return Users.findOne({ userId: Auth.userID }, + { fields: { role: 1 } }).role === ROLE_MODERATOR; + } + + // to be used soon (Paulo) + getNumberOfPublishers() { + return VideoStreams.find({ meetingId: Auth.meetingID }).count(); + } + + isProfileBetter (newProfileId, originalProfileId) { + return CAMERA_PROFILES.findIndex(({ id }) => id === newProfileId) + > CAMERA_PROFILES.findIndex(({ id }) => id === originalProfileId); + } + + applyBitrate (peer, bitrate) { + const peerConnection = peer.peerConnection; + if ('RTCRtpSender' in window + && 'setParameters' in window.RTCRtpSender.prototype + && 'getParameters' in window.RTCRtpSender.prototype) { + peerConnection.getSenders().forEach(sender => { + const { track } = sender; + if (track && track.kind === 'video') { + const parameters = sender.getParameters(); + if (!parameters.encodings) { + parameters.encodings = [{}]; + } + + const normalizedBitrate = bitrate * 1000; + // Only reset bitrate if it changed in some way to avoid enconder fluctuations + if (parameters.encodings[0].maxBitrate !== normalizedBitrate) { + parameters.encodings[0].maxBitrate = normalizedBitrate; + sender.setParameters(parameters) + .then(() => { + logger.info({ + logCode: 'video_provider_bitratechange', + extraInfo: { bitrate }, + }, `Bitrate changed: ${bitrate}`); + }) + .catch(error => { + logger.warn({ + logCode: 'video_provider_bitratechange_failed', + extraInfo: { bitrate, errorMessage: error.message, errorCode: error.code }, + }, `Bitrate change failed.`); + }); + } + } + }) + } + } + + // Some browsers (mainly iOS Safari) garble the stream if a constraint is + // reconfigured without propagating previous height/width info + reapplyResolutionIfNeeded (track, constraints) { + if (typeof track.getSettings !== 'function') { + return constraints; + } + + const trackSettings = track.getSettings(); + + if (trackSettings.width && trackSettings.height) { + return { + ...constraints, + width: trackSettings.width, + height: trackSettings.height + }; + } else { + return constraints; + } + } + + applyCameraProfile (peer, profileId) { + const profile = CAMERA_PROFILES.find(targetProfile => targetProfile.id === profileId); + + if (!profile) { + logger.warn({ + logCode: 'video_provider_noprofile', + extraInfo: { profileId }, + }, `Apply failed: no camera profile found.`); + return; + } + + // Profile is currently applied or it's better than the original user's profile, + // skip + if (peer.currentProfileId === profileId + || this.isProfileBetter(profileId, peer.originalProfileId)) { + return; + } + + const { bitrate, constraints } = profile; + + if (bitrate) { + this.applyBitrate(peer, bitrate); + } + + if (constraints && typeof constraints === 'object') { + peer.peerConnection.getSenders().forEach(sender => { + const { track } = sender; + if (track && track.kind === 'video' && typeof track.applyConstraints === 'function') { + let normalizedVideoConstraints = this.reapplyResolutionIfNeeded(track, constraints); + track.applyConstraints(normalizedVideoConstraints) + .then(() => { + logger.info({ + logCode: 'video_provider_profile_applied', + extraInfo: { profileId }, + }, `New camera profile applied: ${profileId}`); + peer.currentProfileId = profileId; + }) + .catch(error => { + logger.warn({ + logCode: 'video_provider_profile_apply_failed', + extraInfo: { errorName: error.name, errorCode: error.code }, + }, 'Error applying camera profile'); + }); + } + }); + } + } + + getThreshold (numberOfPublishers) { + let targetThreshold = { threshold: 0, profile: 'original' }; + let finalThreshold = { threshold: 0, profile: 'original' }; + + for(let mapIndex = 0; mapIndex < CAMERA_QUALITY_THRESHOLDS.length; mapIndex++) { + targetThreshold = CAMERA_QUALITY_THRESHOLDS[mapIndex]; + if (targetThreshold.threshold <= numberOfPublishers) { + finalThreshold = targetThreshold; + } + } + + return finalThreshold; + } } const videoService = new VideoService(); @@ -431,12 +742,13 @@ export default { getAuthenticatedURL: () => videoService.getAuthenticatedURL(), isLocalStream: cameraId => videoService.isLocalStream(cameraId), hasVideoStream: () => videoService.hasVideoStream(), - isDisabled: () => videoService.isDisabled(), + disableReason: () => videoService.disableReason(), playStart: cameraId => videoService.playStart(cameraId), getCameraProfile: () => videoService.getCameraProfile(), addCandidateToPeer: (peer, candidate, cameraId) => videoService.addCandidateToPeer(peer, candidate, cameraId), processInboundIceQueue: (peer, cameraId) => videoService.processInboundIceQueue(peer, cameraId), getRole: isLocal => videoService.getRole(isLocal), + getRecord: () => videoService.getRecord(), getSharedDevices: () => videoService.getSharedDevices(), getSkipVideoPreview: fromInterface => videoService.getSkipVideoPreview(fromInterface), getUserParameterProfile: () => videoService.getUserParameterProfile(), @@ -446,4 +758,12 @@ export default { onBeforeUnload: () => videoService.onBeforeUnload(), notify: message => notify(message, 'error', 'video'), updateNumberOfDevices: devices => videoService.updateNumberOfDevices(devices), + applyCameraProfile: (peer, newProfile) => videoService.applyCameraProfile(peer, newProfile), + getThreshold: (numberOfPublishers) => videoService.getThreshold(numberOfPublishers), + isPaginationEnabled: () => videoService.isPaginationEnabled(), + getNumberOfPages: () => videoService.getNumberOfPages(), + getCurrentVideoPageIndex: () => videoService.getCurrentVideoPageIndex(), + getPreviousVideoPage: () => videoService.getPreviousVideoPage(), + getNextVideoPage: () => videoService.getNextVideoPage(), + getPageChangeDebounceTime: () => { return PAGE_CHANGE_DEBOUNCE_TIME }, }; diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-button/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-button/component.jsx index b7d567cb26bf5d7d29bca21161fae77b5827d156..b5ea26fd9c144765d16023e2941c55146fdc8626 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-button/component.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import cx from 'classnames'; import Button from '/imports/ui/components/button/component'; import VideoService from '../service'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import { styles } from './styles'; import { validIOSVersion } from '/imports/ui/components/app/service'; @@ -24,6 +24,18 @@ const intlMessages = defineMessages({ id: 'app.video.videoLocked', description: 'video disabled label', }, + videoConnecting: { + id: 'app.video.connecting', + description: 'video connecting label', + }, + dataSaving: { + id: 'app.video.dataSaving', + description: 'video data saving label', + }, + meteorDisconnected: { + id: 'app.video.clientDisconnected', + description: 'Meteor disconnected label', + }, iOSWarning: { id: 'app.iOSWarning.label', description: 'message indicating to upgrade ios version', @@ -31,16 +43,15 @@ const intlMessages = defineMessages({ }); const propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, hasVideoStream: PropTypes.bool.isRequired, - isDisabled: PropTypes.bool.isRequired, mountVideoPreview: PropTypes.func.isRequired, }; const JoinVideoButton = ({ intl, hasVideoStream, - isDisabled, + disableReason, mountVideoPreview, }) => { const exitVideo = () => hasVideoStream && !VideoService.isMultipleCamerasEnabled(); @@ -57,14 +68,14 @@ const JoinVideoButton = ({ } }; - const label = exitVideo() ? - intl.formatMessage(intlMessages.leaveVideo) : - intl.formatMessage(intlMessages.joinVideo); + const label = exitVideo() + ? intl.formatMessage(intlMessages.leaveVideo) + : intl.formatMessage(intlMessages.joinVideo); return ( <Button data-test="joinVideo" - label={isDisabled ? intl.formatMessage(intlMessages.videoLocked) : label} + label={disableReason ? intl.formatMessage(intlMessages[disableReason]) : label} className={cx(styles.button, hasVideoStream || styles.btn)} onClick={handleOnClick} hideLabel @@ -74,7 +85,7 @@ const JoinVideoButton = ({ ghost={!hasVideoStream} size="lg" circle - disabled={isDisabled} + disabled={!!disableReason} /> ); }; diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-button/container.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-button/container.jsx index 28b44f9707f7e54382090a0c3faa5b7c3a196997..b730020c953f177402147a26df8b9cb46ff8ff33 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-button/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-button/container.jsx @@ -9,7 +9,7 @@ import VideoService from '../service'; const JoinVideoOptionsContainer = (props) => { const { hasVideoStream, - isDisabled, + disableReason, intl, mountModal, ...restProps @@ -19,7 +19,7 @@ const JoinVideoOptionsContainer = (props) => { return ( <JoinVideoButton {...{ - mountVideoPreview, hasVideoStream, isDisabled, ...restProps, + mountVideoPreview, hasVideoStream, disableReason, ...restProps, }} /> ); @@ -27,5 +27,5 @@ const JoinVideoOptionsContainer = (props) => { export default withModalMounter(injectIntl(withTracker(() => ({ hasVideoStream: VideoService.hasVideoStream(), - isDisabled: VideoService.isDisabled() || !Meteor.status().connected, + disableReason: VideoService.disableReason(), }))(JoinVideoOptionsContainer))); diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-button/styles.scss b/bigbluebutton-html5/imports/ui/components/video-provider/video-button/styles.scss index 04ea9476dfec785d793cf76b94bd3a82d7c346a0..9edd67e3c0447377ff3410381b237048ae673aed 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-button/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-button/styles.scss @@ -1,4 +1,4 @@ -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/breakpoints"; @import "/imports/ui/components/actions-bar/styles.scss"; .imageSize { diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/component.jsx index bfe4b551bc8d658fd1605b061c08e6699594c0d1..d2b27787858e30916fa26e349ee73e8d39082bc6 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/component.jsx @@ -9,6 +9,8 @@ import { withDraggableConsumer } from '../../media/webcam-draggable-overlay/cont import AutoplayOverlay from '../../media/autoplay-overlay/component'; import logger from '/imports/startup/client/logger'; import playAndRetry from '/imports/utils/mediaElementPlayRetry'; +import VideoService from '/imports/ui/components/video-provider/service'; +import Button from '/imports/ui/components/button/component'; const propTypes = { streams: PropTypes.arrayOf(PropTypes.object).isRequired, @@ -16,6 +18,8 @@ const propTypes = { webcamDraggableDispatch: PropTypes.func.isRequired, intl: PropTypes.objectOf(Object).isRequired, swapLayout: PropTypes.bool.isRequired, + numberOfPages: PropTypes.number.isRequired, + currentVideoPageIndex: PropTypes.number.isRequired, }; const intlMessages = defineMessages({ @@ -37,6 +41,12 @@ const intlMessages = defineMessages({ autoplayAllowLabel: { id: 'app.videoDock.autoplayAllowLabel', }, + nextPageLabel: { + id: 'app.video.pagination.nextPage', + }, + prevPageLabel: { + id: 'app.video.pagination.prevPage', + }, }); const findOptimalGrid = (canvasWidth, canvasHeight, gutter, aspectRatio, numItems, columns = 1) => { @@ -102,11 +112,13 @@ class VideoList extends Component { this.handleCanvasResize(); window.addEventListener('resize', this.handleCanvasResize, false); + window.addEventListener('layoutSizesSets', this.handleCanvasResize, false); window.addEventListener('videoPlayFailed', this.handlePlayElementFailed); } componentWillUnmount() { window.removeEventListener('resize', this.handleCanvasResize, false); + window.removeEventListener('layoutSizesSets', this.handleCanvasResize, false); window.removeEventListener('videoPlayFailed', this.handlePlayElementFailed); } @@ -207,6 +219,54 @@ class VideoList extends Component { this.ticking = true; } + renderNextPageButton() { + const { intl, numberOfPages, currentVideoPageIndex } = this.props; + + if (!VideoService.isPaginationEnabled() || numberOfPages <= 1) return null; + + const currentPage = currentVideoPageIndex + 1; + const nextPageLabel = intl.formatMessage(intlMessages.nextPageLabel); + const nextPageDetailedLabel = `${nextPageLabel} (${currentPage}/${numberOfPages})`; + + return ( + <Button + role="button" + aria-label={nextPageLabel} + color="primary" + icon="right_arrow" + size="md" + onClick={VideoService.getNextVideoPage} + label={nextPageDetailedLabel} + hideLabel + className={cx(styles.nextPage)} + /> + ); + } + + renderPreviousPageButton() { + const { intl, currentVideoPageIndex, numberOfPages } = this.props; + + if (!VideoService.isPaginationEnabled() || numberOfPages <= 1) return null; + + const currentPage = currentVideoPageIndex + 1; + const prevPageLabel = intl.formatMessage(intlMessages.prevPageLabel); + const prevPageDetailedLabel = `${prevPageLabel} (${currentPage}/${numberOfPages})`; + + return ( + <Button + role="button" + aria-label={prevPageLabel} + color="primary" + icon="left_arrow" + size="md" + onClick={VideoService.getPreviousVideoPage} + label={prevPageDetailedLabel} + hideLabel + className={cx(styles.previousPage)} + /> + ); + } + renderVideoList() { const { intl, @@ -275,6 +335,9 @@ class VideoList extends Component { }} className={canvasClassName} > + + {this.renderPreviousPageButton()} + {!streams.length ? null : ( <div ref={(ref) => { @@ -298,6 +361,9 @@ class VideoList extends Component { handleAllowAutoplay={this.handleAllowAutoplay} /> )} + + {this.renderNextPageButton()} + </div> ); } diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/container.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/container.jsx new file mode 100644 index 0000000000000000000000000000000000000000..7bd122a866c956119436600fb84359e230a33c25 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/container.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { withTracker } from 'meteor/react-meteor-data'; +import VideoList from '/imports/ui/components/video-provider/video-list/component'; +import VideoService from '/imports/ui/components/video-provider/service'; + +const VideoListContainer = ({ children, ...props }) => { + const { streams } = props; + return (!streams.length ? null : <VideoList{...props}>{children}</VideoList>); +}; + +export default withTracker(props => ({ + streams: props.streams, + onMount: props.onMount, + swapLayout: props.swapLayout, + numberOfPages: VideoService.getNumberOfPages(), + currentVideoPageIndex: props.currentVideoPageIndex, +}))(VideoListContainer); diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/styles.scss b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/styles.scss index cd8821a7d2792b3c36efaa1b13cc754cd5cf571f..9d61fd9736f14c7b2da588b9bf5aa89850b30b04 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/styles.scss @@ -1,9 +1,5 @@ -@import "/imports/ui/stylesheets/variables/_all"; -@import "/imports/ui/stylesheets/variables/video"; - -:root { - --color-white-with-transparency: #ffffff40; -} +@import "/imports/ui/stylesheets/variables/breakpoints"; +@import "/imports/ui/stylesheets/variables/placeholders"; .videoCanvas { --cam-dropdown-width: 70%; @@ -24,16 +20,14 @@ .videoList { display: grid; - border-radius: 5px; grid-auto-flow: dense; - grid-gap: 5px; + grid-gap: 1px; - align-items: center; justify-content: center; @include mq($medium-up) { - grid-gap: 10px; + grid-gap: 2px; } } @@ -57,7 +51,6 @@ position: relative; display: flex; min-width: 100%; - border-radius: 5px; &::after { content: ""; @@ -66,8 +59,7 @@ right: 0; bottom: 0; left: 0; - border: 5px solid var(--color-white-with-transparency); - border-radius: 5px; + border: 5px solid var(--color-primary); opacity: 0; pointer-events: none; @@ -77,7 +69,7 @@ } &.talking::after { - opacity: 1; + opacity: 0.7; } } @@ -86,7 +78,7 @@ height: 100%; width: 100%; object-fit: contain; - border-radius: 5px; + background-color: var(--color-black); } .cursorGrab{ @@ -97,16 +89,6 @@ cursor: grabbing; } -@keyframes spin { - from { - transform: rotate(0deg); - } - - to { - transform: rotate(-360deg); - } -} - .videoContainer{ width: 100%; height: 100%; @@ -114,41 +96,30 @@ .connecting { @extend %media-area; + display: flex; + justify-content: center; + align-items: center; position: absolute; - color: var(--color-white-with-transparency); - font-size: 2.5rem; - text-align: center; white-space: nowrap; z-index: 1; + vertical-align: middle; + margin: 0 -0.25em 0 0; + border-radius: 1px; + opacity: 1; - - &::after { - content: ''; - display: inline-block; - height: 100%; - vertical-align: middle; - margin: 0 -0.25em 0 0; - - [dir="rtl"] & { - margin: 0 0 0 -0.25em - } + [dir="rtl"] & { + margin: 0 0 0 -0.25em } +} - &::before { - content: "\e949"; - /* ascii code for the ellipsis character */ - font-family: 'bbb-icons' !important; - display: inline-block; - - :global(.animationsEnabled) & { - animation: spin 4s infinite linear; - } - } +.loadingText { + @extend %text-elipsis; + color: var(--color-white); + font-size: 100%; } .media { @extend %media-area; - background-color: var(--color-gray); } .info { @@ -157,7 +128,7 @@ bottom: 5px; left: 5px; right: 5px; - justify-content: center; + justify-content: space-between; align-items: center; height: 1.25rem; z-index: 2; @@ -165,7 +136,6 @@ .dropdown, .dropdownFireFox { - flex: 1; display: flex; outline: none !important; width: var(--cam-dropdown-width); @@ -185,12 +155,12 @@ .userName { @extend %text-elipsis; position: relative; - background-color: var(--color-black); - opacity: .5; - color: var(--color-white); - font-size: 80%; - border-radius: 15px; + // Keep the background with 0.5 opacity, but leave the text with 1 + background-color: rgba(0, 0, 0, 0.5); + border-radius: 1px; + color: var(--color-off-white); padding: 0 1rem 0 .5rem !important; + font-size: 80%; } .noMenu { @@ -255,3 +225,35 @@ .voice { background-color: var(--color-success); } + +.nextPage, +.previousPage{ + color: var(--color-white); + width: var(--md-padding-x); + + i { + [dir="rtl"] & { + -webkit-transform: scale(-1, 1); + -moz-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + -o-transform: scale(-1, 1); + transform: scale(-1, 1); + } + } +} + +.nextPage { + margin-left: 1px; + + @include mq($medium-up) { + margin-left: 2px; + } +} + +.previousPage { + margin-right: 1px; + + @include mq($medium-up) { + margin-right: 2px; + } +} diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx index 12879320f4fa323694328726016963b44f498776..f95f0f6afc441c018bf5b3025926f660d7fdd671 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx @@ -62,10 +62,6 @@ class VideoListItem extends Component { const tagFailedEvent = new CustomEvent('videoPlayFailed', { detail: { mediaTag: elem } }); window.dispatchEvent(tagFailedEvent); } - logger.warn({ - logCode: 'videolistitem_component_play_maybe_error', - extraInfo: { error }, - }, `Could not play video tag due to ${error.name}`); }); } }; @@ -155,14 +151,16 @@ class VideoListItem extends Component { const isFirefox = (result && result.name) ? result.name.includes('firefox') : false; return ( - <div className={cx({ + <div data-test={voiceUser.talking ? 'webcamItemTalkingUser' : 'webcamItem'} className={cx({ [styles.content]: true, [styles.talking]: voiceUser.talking, })} > { - !videoIsReady - && <div data-test="webcamConnecting" className={styles.connecting} /> + !videoIsReady && + <div data-test="webcamConnecting" className={styles.connecting}> + <span className={styles.loadingText}>{name}</span> + </div> } <div className={styles.videoContainer} @@ -170,7 +168,7 @@ class VideoListItem extends Component { > <video muted - data-test="videoContainer" + data-test={this.mirrorOwnWebcam ? 'mirroredVideoContainer' : 'videoContainer'} className={cx({ [styles.media]: true, [styles.cursorGrab]: !webcamDraggableState.dragging @@ -185,7 +183,8 @@ class VideoListItem extends Component { /> {videoIsReady && this.renderFullscreenButton()} </div> - <div className={styles.info}> + { videoIsReady && + <div className={styles.info}> {enableVideoMenu && availableActions.length >= 3 ? ( <Dropdown className={isFirefox ? styles.dropdownFireFox : styles.dropdown}> @@ -216,6 +215,7 @@ class VideoListItem extends Component { {voiceUser.muted && !voiceUser.listenOnly ? <Icon className={styles.muted} iconName="unmute_filled" /> : null} {voiceUser.listenOnly ? <Icon className={styles.voice} iconName="listen" /> : null} </div> + } </div> ); } diff --git a/bigbluebutton-html5/imports/ui/components/waiting-users/component.jsx b/bigbluebutton-html5/imports/ui/components/waiting-users/component.jsx index 131f2bd848338ff11cfda901bd3422daffec2328..658ec84000e5772b727da0b8b8e8c77b096b6ff1 100755 --- a/bigbluebutton-html5/imports/ui/components/waiting-users/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/waiting-users/component.jsx @@ -66,13 +66,14 @@ const getNameInitials = (name) => { return nameInitials.replace(/^\w/, c => c.toUpperCase()); } -const renderGuestUserItem = (name, color, handleAccept, handleDeny, role, sequence, userId, intl) => ( +const renderGuestUserItem = (name, color, handleAccept, handleDeny, role, sequence, userId, avatar, intl) => ( <div key={`userlist-item-${userId}`} className={styles.listItem}> <div key={`user-content-container-${userId}`} className={styles.userContentContainer}> <div key={`user-avatar-container-${userId}`} className={styles.userAvatar}> <UserAvatar key={`user-avatar-${userId}`} moderator={role === 'MODERATOR'} + avatar={avatar} color={color} > {getNameInitials(name)} @@ -123,6 +124,7 @@ const renderPendingUsers = (message, usersArray, action, intl) => { user.role, idx + 1, user.intId, + user.avatar, intl, ))} </div> diff --git a/bigbluebutton-html5/imports/ui/components/waiting-users/styles.scss b/bigbluebutton-html5/imports/ui/components/waiting-users/styles.scss index ce87bc18c0de67924a69b0f066e6243c5bbe1ceb..4e5dafedbeeb757477131e22f57f5cb8514cabcd 100644 --- a/bigbluebutton-html5/imports/ui/components/waiting-users/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/waiting-users/styles.scss @@ -1,5 +1,5 @@ @import "/imports/ui/stylesheets/mixins/focus"; -@import "/imports/ui/stylesheets/variables/_all"; +@import "/imports/ui/stylesheets/variables/placeholders"; .panel { background-color: #fff; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/poll/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/poll/component.jsx index c6382af1ef3f2d93f758617505d43f7055d784fd..187b14ca8bd95f0cc5492b94577b560c7c0641a9 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/poll/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/poll/component.jsx @@ -1,8 +1,16 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import PollService from '/imports/ui/components/poll/service'; -import { injectIntl, intlShape } from 'react-intl'; +import { injectIntl, defineMessages } from 'react-intl'; import styles from './styles'; +import { prototype } from 'clipboard'; + +const intlMessages = defineMessages({ + pollResultAria: { + id: 'app.whiteboard.annotations.pollResult', + description: 'aria label used in poll result string', + }, +}); class PollDrawComponent extends Component { constructor(props) { @@ -424,7 +432,7 @@ class PollDrawComponent extends Component { } return ( - <g> + <g aria-hidden> <rect x={outerRect.x} y={outerRect.y} @@ -575,7 +583,7 @@ class PollDrawComponent extends Component { return this.renderLine(lineToMeasure); } return ( - <g> + <g aria-hidden> {textArray.map(line => this.renderLine(line))} <text fontFamily="Arial" @@ -591,9 +599,17 @@ class PollDrawComponent extends Component { } render() { - const { prepareToDisplay } = this.state; + const { intl } = this.props; + const { prepareToDisplay, textArray } = this.state; + + let ariaResultLabel = `${intl.formatMessage(intlMessages.pollResultAria)}: `; + textArray.map((t, idx) => { + const pollLine = t.slice(0, -1); + ariaResultLabel += `${idx > 0 ? ' |' : ''} ${pollLine.join(' | ')}`; + }); + return ( - <g> + <g aria-label={ariaResultLabel}> {prepareToDisplay ? this.renderTestStrings() : this.renderPoll() @@ -606,7 +622,7 @@ class PollDrawComponent extends Component { export default injectIntl(PollDrawComponent); PollDrawComponent.propTypes = { - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, // Defines an annotation object, which contains all the basic info we need to draw a line annotation: PropTypes.shape({ id: PropTypes.string.isRequired, diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/poll/styles.scss b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/poll/styles.scss index dda7863fd032bf5e88804007c37d14724d4fd678..8d79e03e1e4d5c55597fc7ffdcaae30ec4e48aa2 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/poll/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/poll/styles.scss @@ -1,5 +1,3 @@ -@import "/imports/ui/stylesheets/variables/_all"; - :root { --poll-annotation-gray: #333333; } diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/component.jsx index ecd3bae28befcdc945fa4b5fa25bbab6b6050810..036aa3dd8786757df60f24e7de2c2e1f4922772a 100755 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/component.jsx @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; import { HEXToINTColor, INTToHEXColor } from '/imports/utils/hexInt'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import browser from 'browser-detect'; import { noop } from 'lodash'; import KEY_CODES from '/imports/utils/keyCodes'; @@ -796,7 +796,7 @@ WhiteboardToolbar.defaultProps = { colors: ANNOTATION_COLORS, thicknessRadiuses: THICKNESS_RADIUSES, fontSizes: FONT_SIZES, - intl: intlShape, + intl: {}, }; WhiteboardToolbar.propTypes = { @@ -833,7 +833,7 @@ WhiteboardToolbar.propTypes = { value: PropTypes.number.isRequired, }).isRequired), - intl: intlShape, + intl: PropTypes.object.isRequired, }; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/styles.scss b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/styles.scss index 4bd4361644b8528dc1cd82717e52352cab91a935..ed71d1d4a326897bcc358a4b621ba28051507b76 100755 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/styles.scss @@ -1,4 +1,4 @@ -@import "../../../stylesheets/variables/_all"; +@import "../../../stylesheets/variables/breakpoints"; :root { --toolbar-margin: .8rem; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-submenu/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-submenu/component.jsx index 9df18ca12763a9dd0a999f0178c6e0c22ee767b5..a701d452278b3416d87f5e0ffacf63c77c97bc83 100755 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-submenu/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-submenu/component.jsx @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; -import { defineMessages, injectIntl, intlShape } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import _ from 'lodash'; import { styles } from '../styles'; import ToolbarSubmenuItem from '../toolbar-submenu-item/component'; @@ -307,7 +307,7 @@ ToolbarSubmenu.propTypes = { }), ]).isRequired, customIcon: PropTypes.bool.isRequired, - intl: intlShape.isRequired, + intl: PropTypes.object.isRequired, }; export default injectIntl(ToolbarSubmenu); diff --git a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js index bdf4689646ec499c7acfaa6810409b01a5a8f4c2..9a06d6f12a592575fc5fe38b283056dabc6e056f 100755 --- a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js +++ b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js @@ -16,7 +16,7 @@ const MEDIA = Meteor.settings.public.media; const MEDIA_TAG = MEDIA.mediaTag; const ECHO_TEST_NUMBER = MEDIA.echoTestNumber; const MAX_LISTEN_ONLY_RETRIES = 1; -const LISTEN_ONLY_CALL_TIMEOUT_MS = 15000; +const LISTEN_ONLY_CALL_TIMEOUT_MS = MEDIA.listenOnlyCallTimeout || 15000; const CALL_STATES = { STARTED: 'started', @@ -280,6 +280,31 @@ class AudioManager { return this.bridge.transferCall(this.onAudioJoin.bind(this)); } + onVoiceUserChanges(fields) { + if (fields.muted !== undefined && fields.muted !== this.isMuted) { + let muteState; + this.isMuted = fields.muted; + + if (this.isMuted) { + muteState = 'selfMuted'; + this.mute(); + } else { + muteState = 'selfUnmuted'; + this.unmute(); + } + + window.parent.postMessage({ response: muteState }, '*'); + } + + if (fields.talking !== undefined && fields.talking !== this.isTalking) { + this.isTalking = fields.talking; + } + + if (this.isMuted) { + this.isTalking = false; + } + } + onAudioJoin() { this.isConnecting = false; this.isConnected = true; @@ -288,21 +313,8 @@ class AudioManager { if (!this.muteHandle) { const query = VoiceUsers.find({ intId: Auth.userID }, { fields: { muted: 1, talking: 1 } }); this.muteHandle = query.observeChanges({ - changed: (id, fields) => { - if (fields.muted !== undefined && fields.muted !== this.isMuted) { - this.isMuted = fields.muted; - const muteState = this.isMuted ? 'selfMuted' : 'selfUnmuted'; - window.parent.postMessage({ response: muteState }, '*'); - } - - if (fields.talking !== undefined && fields.talking !== this.isTalking) { - this.isTalking = fields.talking; - } - - if (this.isMuted) { - this.isTalking = false; - } - }, + added: (id, fields) => this.onVoiceUserChanges(fields), + changed: (id, fields) => this.onVoiceUserChanges(fields), }); } @@ -562,6 +574,29 @@ class AudioManager { this.autoplayBlocked = true; } } + + setSenderTrackEnabled(shouldEnable) { + // If the bridge is set to listen only mode, nothing to do here. This method + // is solely for muting outbound tracks. + if (this.isListenOnly) return; + + // Bridge -> SIP.js bridge, the only full audio capable one right now + const peer = this.bridge.getPeerConnection(); + peer.getSenders().forEach((sender) => { + const { track } = sender; + if (track && track.kind === 'audio') { + track.enabled = shouldEnable; + } + }); + } + + mute() { + this.setSenderTrackEnabled(false); + } + + unmute() { + this.setSenderTrackEnabled(true); + } } const audioManager = new AudioManager(); diff --git a/bigbluebutton-html5/imports/ui/stylesheets/mixins/_indicators.scss b/bigbluebutton-html5/imports/ui/stylesheets/mixins/_indicators.scss index b57a542eb42a4399ee595d4676077efee9dfede1..3f9c5783394a2bf3e8285650362b13f76430b5ad 100644 --- a/bigbluebutton-html5/imports/ui/stylesheets/mixins/_indicators.scss +++ b/bigbluebutton-html5/imports/ui/stylesheets/mixins/_indicators.scss @@ -1,6 +1,3 @@ -@import "/imports/ui/stylesheets/variables/palette"; -@import "/imports/ui/stylesheets/variables/general"; - @mixin presenterIndicator() { &:before { opacity: 1; diff --git a/bigbluebutton-html5/imports/ui/stylesheets/mixins/focus.scss b/bigbluebutton-html5/imports/ui/stylesheets/mixins/focus.scss index 1d70100fb152964302f66025f42c74cb1a9c69b7..eae8d25bdc0a934212ca311e8745c9c2575a6e4b 100644 --- a/bigbluebutton-html5/imports/ui/stylesheets/mixins/focus.scss +++ b/bigbluebutton-html5/imports/ui/stylesheets/mixins/focus.scss @@ -1,6 +1,3 @@ -@import "/imports/ui/stylesheets/variables/palette"; -@import "/imports/ui/stylesheets/variables/general"; - @mixin elementFocus($color: var(--color-primary)) { &:focus { outline: none; diff --git a/bigbluebutton-html5/imports/ui/stylesheets/variables/_all.scss b/bigbluebutton-html5/imports/ui/stylesheets/variables/_all.scss index 1fa95597203199f7c2b497a8a997c9364a470bb6..e02c29ffbf0de23f539a5be6c346aad68abf6449 100644 --- a/bigbluebutton-html5/imports/ui/stylesheets/variables/_all.scss +++ b/bigbluebutton-html5/imports/ui/stylesheets/variables/_all.scss @@ -1,9 +1,6 @@ @import "./breakpoints"; - -%text-elipsis { - min-width: 0; - display: inline-block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} +@import "./general"; +@import "./palette"; +@import "./typography"; +@import "./video"; +@import "./placeholders"; diff --git a/bigbluebutton-html5/imports/ui/stylesheets/variables/general.scss b/bigbluebutton-html5/imports/ui/stylesheets/variables/general.scss index 54ec568a6499975addce6a096e206da935d4bb42..5e5a1020ade63e9740df8b2ebd52ee6ec5d3a39b 100755 --- a/bigbluebutton-html5/imports/ui/stylesheets/variables/general.scss +++ b/bigbluebutton-html5/imports/ui/stylesheets/variables/general.scss @@ -25,5 +25,15 @@ --indicator-padding-top: 0.7em; --indicator-padding-bottom: 0.7em; + //Miscellaneous --user-indicators-offset: -5px; + --mobile-swap-offset: 3.5rem; + --min-modal-height: 20rem; + --modal-margin: 3rem; + --title-position-left: 2.2rem; + --closeBtn-position-left: 2.5rem; + --description-margin: 3.5rem; + --toast-icon-side: 40px; + --innerToastWidth: 17rem; + --iconWrapperSize: 2rem; } diff --git a/bigbluebutton-html5/imports/ui/stylesheets/variables/palette.scss b/bigbluebutton-html5/imports/ui/stylesheets/variables/palette.scss index 4edfd06d5d23937fc2e0c4cf3106f6c8336891ff..8d5875d8673307c52b617946bc38cb219ef00343 100644 --- a/bigbluebutton-html5/imports/ui/stylesheets/variables/palette.scss +++ b/bigbluebutton-html5/imports/ui/stylesheets/variables/palette.scss @@ -31,4 +31,30 @@ --color-transparent: #ff000000; --color-tip-bg: #333333; + + --color-white-with-transparency: #ffffff40; + + --loader-bg: var(--color-gray-dark); + --loader-bullet: var(--color-white); + --user-list-bg: var(--color-off-white); + + --unread-messages-bg: var(--color-danger); + --user-list-text: var(--color-gray); + + --user-thumbnail-border: var(--color-gray-light); + --user-thumbnail-text: var(--user-thumbnail-border); + + --voice-user-bg: var(--color-success); + --voice-user-text: var(--color-white); + + --moderator-text: var(--color-white); + --moderator-bg: var(--color-primary); + + --sub-name-color: var(--color-gray-light); + + --user-icons-color: var(--color-gray-light); + --user-icons-color-hover: var(--color-gray); + + --list-item-bg-hover: #dce4ed; + --item-focus-border: var(--color-blue-lighter); } diff --git a/bigbluebutton-html5/imports/ui/stylesheets/variables/placeholders.scss b/bigbluebutton-html5/imports/ui/stylesheets/variables/placeholders.scss new file mode 100644 index 0000000000000000000000000000000000000000..7ef1aeb5b3202d25bebf9413348860c271f6a650 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/stylesheets/variables/placeholders.scss @@ -0,0 +1,29 @@ +%no-margin { + margin: 0; +} + +%flex-column { + display: flex; + flex-flow: column; +} + +%flex-row { + display: flex; + flex-flow: row; +} + +%text-elipsis { + min-width: 0; + display: inline-block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +%highContrastOutline { + /* Visible in Windows high-contrast themes */ + outline: transparent; + outline-style: dotted; + outline-width: var(--border-size); +} + diff --git a/bigbluebutton-html5/imports/ui/stylesheets/variables/typography.scss b/bigbluebutton-html5/imports/ui/stylesheets/variables/typography.scss index bf83f8da395fa33535242c4244189bc263016e41..492a95241e8efe68fb598d2e72ba61e68c458f96 100644 --- a/bigbluebutton-html5/imports/ui/stylesheets/variables/typography.scss +++ b/bigbluebutton-html5/imports/ui/stylesheets/variables/typography.scss @@ -7,6 +7,7 @@ --font-size-base: 1rem; --font-size-xl: 1.75rem; + --font-size-larger: 1.5rem; --font-size-large: 1.25rem; --font-size-md: 0.95rem; --font-size-small: 0.875rem; diff --git a/bigbluebutton-html5/imports/ui/stylesheets/variables/video.scss b/bigbluebutton-html5/imports/ui/stylesheets/variables/video.scss index af5c507bbc207eebd32d2a7caea3340089ddc332..484d181e93de4f416b7b7ce89dead63423e4ae29 100644 --- a/bigbluebutton-html5/imports/ui/stylesheets/variables/video.scss +++ b/bigbluebutton-html5/imports/ui/stylesheets/variables/video.scss @@ -1,5 +1,3 @@ -@import "../../stylesheets/variables/_all"; - :root { --video-height: calc((100vh - calc(var(--navbar-height) + var(--actionsbar-height))) * 0.2); --video-ratio: calc(4 / 3); diff --git a/bigbluebutton-html5/package-lock.json b/bigbluebutton-html5/package-lock.json index 51144cdd7a2b0c4c4870893b3440f1130f318726..5101170973e528b346acaf5ee3f47f1ffd477840 100644 --- a/bigbluebutton-html5/package-lock.json +++ b/bigbluebutton-html5/package-lock.json @@ -426,6 +426,43 @@ "kuler": "^2.0.0" } }, + "@formatjs/intl-displaynames": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-1.2.10.tgz", + "integrity": "sha512-GROA2RP6+7Ouu0WnHFF78O5XIU7pBfI19WM1qm93l6MFWibUk67nCfVCK3VAYJkLy8L8ZxjkYT11VIAfvSz8wg==", + "requires": { + "@formatjs/intl-utils": "^2.3.0" + } + }, + "@formatjs/intl-listformat": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-1.4.8.tgz", + "integrity": "sha512-WNMQlEg0e50VZrGIkgD5n7+DAMGt3boKi1GJALfhFMymslJb5i+5WzWxyj/3a929Z6MAFsmzRIJjKuv+BxKAOQ==", + "requires": { + "@formatjs/intl-utils": "^2.3.0" + } + }, + "@formatjs/intl-relativetimeformat": { + "version": "4.5.16", + "resolved": "https://registry.npmjs.org/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-4.5.16.tgz", + "integrity": "sha512-IQ0haY97oHAH5OYUdykNiepdyEWj3SAT+Fp9ZpR85ov2JNiFx+12WWlxlVS8ehdyncC2ZMt/SwFIy2huK2+6/A==", + "requires": { + "@formatjs/intl-utils": "^2.3.0" + } + }, + "@formatjs/intl-unified-numberformat": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@formatjs/intl-unified-numberformat/-/intl-unified-numberformat-3.3.7.tgz", + "integrity": "sha512-KnWgLRHzCAgT9eyt3OS34RHoyD7dPDYhRcuKn+/6Kv2knDF8Im43J6vlSW6Hm1w63fNq3ZIT1cFk7RuVO3Psag==", + "requires": { + "@formatjs/intl-utils": "^2.3.0" + } + }, + "@formatjs/intl-utils": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@formatjs/intl-utils/-/intl-utils-2.3.0.tgz", + "integrity": "sha512-KWk80UPIzPmUg+P0rKh6TqspRw0G6eux1PuJr+zz47ftMaZ9QDwbGzHZbtzWkl5hgayM/qrKRutllRC7D/vVXQ==" + }, "@iamstarkov/listr-update-renderer": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@iamstarkov/listr-update-renderer/-/listr-update-renderer-0.4.1.tgz", @@ -1190,6 +1227,20 @@ "@types/node": "*" } }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "@types/invariant": { + "version": "2.2.33", + "resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.33.tgz", + "integrity": "sha512-/jUNmS8d4bCKdqslfxW6dg/9Gksfzxz67IYfqApHn+HvHlMVXwYv2zpTDnS/yaK9BB0i0GlBTaYci0EFE62Hmw==" + }, "@types/istanbul-lib-coverage": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.2.tgz", @@ -1239,6 +1290,20 @@ "integrity": "sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==", "dev": true }, + "@types/prop-types": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" + }, + "@types/react": { + "version": "16.9.35", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.35.tgz", + "integrity": "sha512-q0n0SsWcGc8nDqH2GJfWQWUOmZSJhXV64CjVN5SvcNti3TdEaA3AH0D8DwNmMdzjMAC/78tB8nAZIlV8yTz+zQ==", + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", @@ -2378,8 +2443,7 @@ "cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" }, "cssom": { "version": "0.4.4", @@ -2619,6 +2683,20 @@ "@babel/runtime": "^7.1.2" } }, + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==" + }, "domexception": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", @@ -2628,12 +2706,22 @@ "webidl-conversions": "^4.0.2" } }, - "dot-prop": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", - "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "domhandler": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz", + "integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==", + "requires": { + "domelementtype": "^2.0.1" + } + }, + "domutils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.1.0.tgz", + "integrity": "sha512-CD9M0Dm1iaHfQ1R/TI+z3/JWp/pgub0j4jIQKH89ARR4ATAV2nbaOQS5XxU9maJP5jHaPdDDQSEHuE2UmpUTKg==", "requires": { - "is-obj": "^2.0.0" + "dom-serializer": "^0.2.1", + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0" } }, "dotenv": { @@ -2686,6 +2774,11 @@ "once": "^1.4.0" } }, + "entities": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", + "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==" + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3877,9 +3970,12 @@ } }, "hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } }, "hosted-git-info": { "version": "2.8.8", @@ -3901,6 +3997,17 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "htmlparser2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz", + "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0", + "domutils": "^2.0.0", + "entities": "^2.0.0" + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -4287,29 +4394,25 @@ } }, "intl-format-cache": { - "version": "2.2.9", - "resolved": "https://registry.npmjs.org/intl-format-cache/-/intl-format-cache-2.2.9.tgz", - "integrity": "sha512-Zv/u8wRpekckv0cLkwpVdABYST4hZNTDaX7reFetrYTJwxExR2VyTqQm+l0WmL0Qo8Mjb9Tf33qnfj0T7pjxdQ==" + "version": "4.2.28", + "resolved": "https://registry.npmjs.org/intl-format-cache/-/intl-format-cache-4.2.28.tgz", + "integrity": "sha512-yFACAtiacQj2nPfFSyDd/ZRgDFDDtw55cmqdYux7ncqrqvaMTajf3Biuc4a3HAWNuMvk0r2VHBfwy2YmOmAZ+A==" }, "intl-messageformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-2.2.0.tgz", - "integrity": "sha1-NFvNRt5jC3aDMwwuUhd/9eq0hPw=", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-7.8.4.tgz", + "integrity": "sha512-yS0cLESCKCYjseCOGXuV4pxJm/buTfyCJ1nzQjryHmSehlptbZbn9fnlk1I9peLopZGGbjj46yHHiTAEZ1qOTA==", "requires": { - "intl-messageformat-parser": "1.4.0" + "intl-format-cache": "^4.2.21", + "intl-messageformat-parser": "^3.6.4" } }, "intl-messageformat-parser": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/intl-messageformat-parser/-/intl-messageformat-parser-1.4.0.tgz", - "integrity": "sha1-tD1FqXRoytvkQzHXS7Ho3qRPwHU=" - }, - "intl-relativeformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/intl-relativeformat/-/intl-relativeformat-2.2.0.tgz", - "integrity": "sha512-4bV/7kSKaPEmu6ArxXf9xjv1ny74Zkwuey8Pm01NH4zggPP7JHwg2STk8Y3JdspCKRDriwIyLRfEXnj2ZLr4Bw==", + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/intl-messageformat-parser/-/intl-messageformat-parser-3.6.4.tgz", + "integrity": "sha512-RgPGwue0mJtoX2Ax8EmMzJzttxjnva7gx0Q7mKJ4oALrTZvtmCeAw5Msz2PcjW4dtCh/h7vN/8GJCxZO1uv+OA==", "requires": { - "intl-messageformat": "^2.0.0" + "@formatjs/intl-unified-numberformat": "^3.2.0" } }, "invariant": { @@ -4486,11 +4589,6 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" - }, "is-observable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz", @@ -7141,33 +7239,33 @@ "dev": true }, "meteor-node-stubs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-0.4.1.tgz", - "integrity": "sha512-UO2OStvLOKoApmOdIP5eCqoLaa/ritMXRg4ffJVdkNLEsczzPvTjgC0Mxk4cM4R8MZkwll90FYgjDf5qUTJdMA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-1.0.1.tgz", + "integrity": "sha512-I4PE/z7eAl45XEsebHA4pcQbgjqEdK3EBGgiUoIZBi3bMQcMq6blLWZo+WdtK4Or9X4NJOiYWw4GmHiubr3egA==", "requires": { "assert": "^1.4.1", - "browserify-zlib": "^0.1.4", - "buffer": "^4.9.1", + "browserify-zlib": "^0.2.0", + "buffer": "^5.2.1", "console-browserify": "^1.1.0", "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.7", - "events": "^1.1.1", - "https-browserify": "0.0.1", - "os-browserify": "^0.2.1", - "path-browserify": "0.0.0", - "process": "^0.11.9", - "punycode": "^1.4.1", + "crypto-browserify": "^3.12.0", + "domain-browser": "^1.2.0", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "^1.0.0", + "process": "^0.11.10", + "punycode": "^2.1.1", "querystring-es3": "^0.2.1", - "readable-stream": "^2.3.6", - "stream-browserify": "^2.0.1", - "stream-http": "^2.8.0", - "string_decoder": "^1.1.0", - "timers-browserify": "^1.4.2", - "tty-browserify": "0.0.0", + "readable-stream": "^3.3.0", + "stream-browserify": "^2.0.2", + "stream-http": "^3.0.0", + "string_decoder": "^1.2.0", + "timers-browserify": "^2.0.10", + "tty-browserify": "0.0.1", "url": "^0.11.0", - "util": "^0.10.3", - "vm-browserify": "0.0.4" + "util": "^0.11.1", + "vm-browserify": "^1.1.0" }, "dependencies": { "asn1.js": { @@ -7184,6 +7282,15 @@ "bundled": true, "requires": { "util": "0.10.3" + }, + "dependencies": { + "util": { + "version": "0.10.3", + "bundled": true, + "requires": { + "inherits": "2.0.1" + } + } } }, "base64-js": { @@ -7220,12 +7327,13 @@ } }, "browserify-des": { - "version": "1.0.1", + "version": "1.0.2", "bundled": true, "requires": { "cipher-base": "^1.0.1", "des.js": "^1.0.0", - "inherits": "^2.0.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" } }, "browserify-rsa": { @@ -7250,19 +7358,18 @@ } }, "browserify-zlib": { - "version": "0.1.4", + "version": "0.2.0", "bundled": true, "requires": { - "pako": "~0.2.0" + "pako": "~1.0.5" } }, "buffer": { - "version": "4.9.1", + "version": "5.2.1", "bundled": true, "requires": { "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" + "ieee754": "^1.1.4" } }, "buffer-xor": { @@ -7370,7 +7477,7 @@ "bundled": true }, "elliptic": { - "version": "6.4.0", + "version": "6.5.3", "bundled": true, "requires": { "bn.js": "^4.4.0", @@ -7383,7 +7490,7 @@ } }, "events": { - "version": "1.1.1", + "version": "3.0.0", "bundled": true }, "evp_bytestokey": { @@ -7403,11 +7510,11 @@ } }, "hash.js": { - "version": "1.1.3", + "version": "1.1.7", "bundled": true, "requires": { "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.0" + "minimalistic-assert": "^1.0.1" }, "dependencies": { "inherits": { @@ -7426,15 +7533,11 @@ } }, "https-browserify": { - "version": "0.0.1", + "version": "1.0.0", "bundled": true }, "ieee754": { - "version": "1.1.11", - "bundled": true - }, - "indexof": { - "version": "0.0.1", + "version": "1.1.13", "bundled": true }, "inherits": { @@ -7446,11 +7549,12 @@ "bundled": true }, "md5.js": { - "version": "1.3.4", + "version": "1.3.5", "bundled": true, "requires": { "hash-base": "^3.0.0", - "inherits": "^2.0.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" } }, "miller-rabin": { @@ -7470,30 +7574,31 @@ "bundled": true }, "os-browserify": { - "version": "0.2.1", + "version": "0.3.0", "bundled": true }, "pako": { - "version": "0.2.9", + "version": "1.0.10", "bundled": true }, "parse-asn1": { - "version": "5.1.1", + "version": "5.1.4", "bundled": true, "requires": { "asn1.js": "^4.0.0", "browserify-aes": "^1.0.0", "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3" + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" } }, "path-browserify": { - "version": "0.0.0", + "version": "1.0.0", "bundled": true }, "pbkdf2": { - "version": "3.0.16", + "version": "3.0.17", "bundled": true, "requires": { "create-hash": "^1.1.2", @@ -7512,18 +7617,19 @@ "bundled": true }, "public-encrypt": { - "version": "4.0.2", + "version": "4.0.3", "bundled": true, "requires": { "bn.js": "^4.1.0", "browserify-rsa": "^4.0.0", "create-hash": "^1.1.0", "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1" + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" } }, "punycode": { - "version": "1.4.1", + "version": "2.1.1", "bundled": true }, "querystring": { @@ -7535,7 +7641,7 @@ "bundled": true }, "randombytes": { - "version": "2.0.6", + "version": "2.1.0", "bundled": true, "requires": { "safe-buffer": "^5.1.0" @@ -7550,16 +7656,12 @@ } }, "readable-stream": { - "version": "2.3.6", + "version": "3.3.0", "bundled": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "dependencies": { "inherits": { @@ -7580,6 +7682,10 @@ "version": "5.1.2", "bundled": true }, + "setimmediate": { + "version": "1.0.5", + "bundled": true + }, "sha.js": { "version": "2.4.11", "bundled": true, @@ -7589,44 +7695,67 @@ } }, "stream-browserify": { - "version": "2.0.1", + "version": "2.0.2", "bundled": true, "requires": { "inherits": "~2.0.1", "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "bundled": true + } + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, "stream-http": { - "version": "2.8.1", + "version": "3.0.0", "bundled": true, "requires": { "builtin-status-codes": "^3.0.0", "inherits": "^2.0.1", - "readable-stream": "^2.3.3", - "to-arraybuffer": "^1.0.0", + "readable-stream": "^3.0.6", "xtend": "^4.0.0" } }, "string_decoder": { - "version": "1.1.1", + "version": "1.2.0", "bundled": true, "requires": { "safe-buffer": "~5.1.0" } }, "timers-browserify": { - "version": "1.4.2", + "version": "2.0.10", "bundled": true, "requires": { - "process": "~0.11.0" + "setimmediate": "^1.0.4" } }, - "to-arraybuffer": { - "version": "1.0.1", - "bundled": true - }, "tty-browserify": { - "version": "0.0.0", + "version": "0.0.1", "bundled": true }, "url": { @@ -7644,10 +7773,16 @@ } }, "util": { - "version": "0.10.3", + "version": "0.11.1", "bundled": true, "requires": { - "inherits": "2.0.1" + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "bundled": true + } } }, "util-deprecate": { @@ -7655,11 +7790,8 @@ "bundled": true }, "vm-browserify": { - "version": "0.0.4", - "bundled": true, - "requires": { - "indexof": "0.0.1" - } + "version": "1.1.0", + "bundled": true }, "xtend": { "version": "4.0.1", @@ -8201,6 +8333,11 @@ "error-ex": "^1.2.0" } }, + "parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha1-8r0iH2zJcKk42IVWq8WJyqqiveE=" + }, "parse5": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", @@ -8465,20 +8602,32 @@ } }, "postcss-nested": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-4.1.0.tgz", - "integrity": "sha512-owY13v4s3WWTUjsT1H1Cgpa4veHjcBJ/FqbgORe1dJIKpggbFoh6ww+zUP0nzrvy7fXGihcuFhJQj3eXtaWXsw==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-4.2.3.tgz", + "integrity": "sha512-rOv0W1HquRCamWy2kFl3QazJMMe1ku6rCFoAAH+9AcxdbpDeBr6k968MLWuLjvjMcGEip01ak09hKOEgpK9hvw==", "requires": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^3.1.1" + "postcss": "^7.0.32", + "postcss-selector-parser": "^6.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", + "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + } } }, "postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", + "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", "requires": { - "dot-prop": "^5.2.0", + "cssesc": "^3.0.0", "indexes-of": "^1.0.1", "uniq": "^1.0.1" } @@ -8750,15 +8899,22 @@ "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" }, "react-intl": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-2.7.2.tgz", - "integrity": "sha512-3dcNGLqEw2FKkX+1L2WYLgjP0MVJkvWuVd1uLcnwifIQe8JQvnd9Bss4hb4Gvg/YhBIRcs4LM6C2bAgyklucjw==", - "requires": { - "hoist-non-react-statics": "^2.5.5", - "intl-format-cache": "^2.0.5", - "intl-messageformat": "^2.1.0", - "intl-relativeformat": "^2.1.0", - "invariant": "^2.1.1" + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-3.12.1.tgz", + "integrity": "sha512-cgumW29mwROIqyp8NXStYsoIm27+8FqnxykiLSawWjOxGIBeLuN/+p2srei5SRIumcJefOkOIHP+NDck05RgHg==", + "requires": { + "@formatjs/intl-displaynames": "^1.2.0", + "@formatjs/intl-listformat": "^1.4.1", + "@formatjs/intl-relativetimeformat": "^4.5.9", + "@formatjs/intl-unified-numberformat": "^3.2.0", + "@formatjs/intl-utils": "^2.2.0", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/invariant": "^2.2.31", + "hoist-non-react-statics": "^3.3.2", + "intl-format-cache": "^4.2.21", + "intl-messageformat": "^7.8.4", + "intl-messageformat-parser": "^3.6.4", + "shallow-equal": "^1.2.1" } }, "react-is": { @@ -8814,6 +8970,15 @@ "prop-types": "^15.5.0" } }, + "react-tether": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/react-tether/-/react-tether-2.0.7.tgz", + "integrity": "sha512-OZAMoT0y1//SN357HiJKic+Ax/kMe3CwdaDT+05P/DHMR9adTYH2RTMDZMjw/OGMmLlBFg6UrDFXiulVKKIBRw==", + "requires": { + "prop-types": "^15.6.2", + "tether": "^1.4.5" + } + }, "react-toastify": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-4.5.2.tgz", @@ -9387,6 +9552,17 @@ } } }, + "sanitize-html": { + "version": "1.27.3", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.27.3.tgz", + "integrity": "sha512-79tcPlgJ3fuK0/TtUCIBdPeQSvktTSTJP9O/dzrteaO98qw5UV6CATh3ZyPjUzv1LtNjHDlhbq9XOXiKf0zA1w==", + "requires": { + "htmlparser2": "^4.1.0", + "lodash": "^4.17.15", + "parse-srcset": "^1.0.2", + "postcss": "^7.0.27" + } + }, "sass-graph": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.5.tgz", @@ -9494,6 +9670,11 @@ "crypt": ">= 0.0.1" } }, + "shallow-equal": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", + "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==" + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -10195,6 +10376,11 @@ "minimatch": "^3.0.4" } }, + "tether": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/tether/-/tether-1.4.7.tgz", + "integrity": "sha512-Z0J1aExjoFU8pybVkQAo/vD2wfSO63r+XOPfWQMC5qtf1bI7IWqNk4MiyBcgvvnY8kqnY06dVdvwTK2S3PU/Fw==" + }, "text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", diff --git a/bigbluebutton-html5/package.json b/bigbluebutton-html5/package.json index 5ea8c1f511e5049ad54348de480293df55c882dc..4c9ca092e1ae7aadfc9096de73d985ed92198ada 100755 --- a/bigbluebutton-html5/package.json +++ b/bigbluebutton-html5/package.json @@ -49,9 +49,9 @@ "langmap": "0.0.16", "lodash": "^4.17.19", "makeup-screenreader-trap": "0.0.5", - "meteor-node-stubs": "^0.4.1", - "node-sass": "^4.13.1", - "postcss-nested": "4.1.0", + "meteor-node-stubs": "^1.0.1", + "node-sass": "^4.14.1", + "postcss-nested": "4.2.3", "probe-image-size": "^4.1.1", "prop-types": "^15.7.2", "re-resizable": "^4.11.0", @@ -62,17 +62,19 @@ "react-dom": "^16.12.0", "react-draggable": "^3.3.2", "react-dropzone": "^7.0.1", - "react-intl": "~2.7.2", + "react-intl": "^3.12.1", "react-modal": "~3.6.1", "react-player": "^2.5.0", "react-render-in-browser": "^1.1.1", "react-tabs": "^2.3.1", + "react-tether": "^2.0.7", "react-toastify": "^4.5.2", "react-toggle": "~4.0.2", "react-transition-group": "^2.9.0", "react-virtualized": "^9.21.1", "reconnecting-websocket": "~v4.1.10", "redis": "~2.8.0", + "sanitize-html": "^1.27.1", "sdp-transform": "2.7.0", "string-hash": "~1.1.3", "tippy.js": "^5.1.3", diff --git a/bigbluebutton-html5/private/config/fallbackLocales.json b/bigbluebutton-html5/private/config/fallbackLocales.json new file mode 100644 index 0000000000000000000000000000000000000000..a9c77806238143602500c2d26d948f2214b79cb6 --- /dev/null +++ b/bigbluebutton-html5/private/config/fallbackLocales.json @@ -0,0 +1,18 @@ +{ + "hy": { + "englishName": "Armenian", + "nativeName": "Õ€Õ¡ÕµÕ¥Ö€Õ¥Õ¶" + }, + "ka": { + "englishName": "Georgian", + "nativeName": "ქáƒáƒ თული" + }, + "lo-LA": { + "englishName": "Lao", + "nativeName": "ລາວ" + }, + "oc": { + "englishName": "Occitan", + "nativeName": "Occitan" + } +} diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 849b5dac29e8ff17f97afaac3792c4c87b400441..825bee01405ef78243523ae33878c848589b4658 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -27,6 +27,7 @@ public: ipv4FallbackDomain: "" allowLogout: true allowFullscreen: true + preloadNextSlides: 2 mutedAlert: enabled: true interval: 200 @@ -34,6 +35,10 @@ public: duration: 4000 remainingTimeThreshold: 30 remainingTimeAlertThreshold: 1 + # Warning: increasing the limit of breakout rooms per meeting + # can generate excessive overhead to the server. We recommend + # this value to be kept under 12. + breakoutRoomLimit: 8 defaultSettings: application: animations: true @@ -94,6 +99,16 @@ public: packetLostThreshold: 10 kurento: wsUrl: HOST + # Valid for video-provider. Time (ms) before its WS connection times out + # and tries to reconnect. + wsConnectionTimeout: 4000 + cameraTimeouts: + # Base camera timeout: used as the camera *sharing* timeout and + # as the minimum camera subscribe reconnection timeout + baseTimeout: 15000 + # Max timeout: used as the max camera subscribe reconnection timeout. Each + # subscribe reattempt increases the reconnection timer up to this + maxTimeout: 60000 chromeDefaultExtensionKey: akgoaoikmbmhcopjgakkcepdgdgkjfbc chromeDefaultExtensionLink: https://chrome.google.com/webstore/detail/bigbluebutton-screenshare/akgoaoikmbmhcopjgakkcepdgdgkjfbc chromeExtensionKey: KEY @@ -113,6 +128,12 @@ public: - window - screen firefoxScreenshareSource: window + # cameraProfiles is an array of: + # - id: profile identifier + # name: human-readable profile name + # bitrate + # hidden: whether this profile will be hidden in the video preview dropdown + # constraints: a video media constraints dictionary (without the video key) cameraProfiles: # id: unique identifier of the profile # name: name of the profile visible to users @@ -125,28 +146,104 @@ public: # # Examples: # width: requested width of the camera stream # frameRate: requested framerate + - id: low-u30 + name: low-u30 + bitrate: 30 + hidden: true + constraints: + frameRate: 3 + - id: low-u25 + name: low-u25 + bitrate: 40 + hidden: true + constraints: + frameRate: 3 + - id: low-u20 + name: low-u20 + bitrate: 50 + hidden: true + constraints: + frameRate: 5 + - id: low-u15 + name: low-u15 + bitrate: 70 + hidden: true + constraints: + frameRate: 8 + - id: low-u12 + name: low-u12 + bitrate: 90 + hidden: true + constraints: + frameRate: 10 + - id: low-u8 + name: low-u8 + bitrate: 100 + hidden: true + constraints: + frameRate: 10 - id: low - name: Low quality + name: Low default: false bitrate: 100 - id: medium - name: Medium quality + name: Medium default: true bitrate: 200 - id: high - name: High quality + name: High default: false bitrate: 500 + constraints: + width: 1280 + frameRate: 15 - id: hd name: High definition default: false bitrate: 800 + constraints: + width: 1280 + frameRate: 30 enableScreensharing: true enableVideo: true enableVideoMenu: true enableListenOnly: true autoShareWebcam: false skipVideoPreview: false + # Entry `thresholds` is an array of: + # - threshold: minimum number of cameras being shared for profile to applied + # profile: a camera profile id from the cameraProfiles configuration array + # that will be applied to all cameras when threshold is hit + cameraQualityThresholds: + enabled: false + thresholds: + - threshold: 8 + profile: low-u8 + - threshold: 12 + profile: low-u12 + - threshold: 15 + profile: low-u15 + - threshold: 20 + profile: low-u20 + - threshold: 25 + profile: low-u25 + - threshold: 30 + profile: low-u30 + pagination: + # whether to globally enable or disable pagination. + enabled: false + # how long (in ms) the negotiation will be debounced after a page change. + pageChangeDebounceTime: 2500 + # video page sizes for DESKTOP endpoints. It stands for the number of SUBSCRIBER streams. + # PUBLISHERS aren't accounted for . + # A page size of 0 (zero) means that the page size is unlimited (disabled). + desktopPageSizes: + moderator: 0 + viewer: 5 + # video page sizes for MOBILE endpoints + mobilePageSizes: + moderator: 2 + viewer: 2 pingPong: clearUsersInSeconds: 180 pongTimeInSeconds: 15 @@ -209,6 +306,7 @@ public: callHangupMaximumRetries: 10 echoTestNumber: 'echo' relayOnlyOnReconnect: false + listenOnlyCallTimeout: 15000 stats: enabled: true interval: 2000 @@ -356,9 +454,20 @@ public: - pencil - hand clientLog: - server: { enabled: true, level: info } - console: { enabled: true, level: debug } - external: { enabled: false, level: info, url: https://LOG_HOST/html5Log, method: POST, throttleInterval: 400, flushOnClose: true, logTag: "" } + server: + enabled: true + level: info + console: + enabled: true + level: debug + external: + enabled: false + level: info + url: https://LOG_HOST/html5Log + method: POST + throttleInterval: 400 + flushOnClose: true + logTag: "" private: app: host: 127.0.0.1 diff --git a/bigbluebutton-html5/private/locales/ar.json b/bigbluebutton-html5/private/locales/ar.json index 91d7dbacd538526dbdd0192bf78470ae7a447a58..64e11ac818e9d22156662230d960fc32670fc40e 100644 --- a/bigbluebutton-html5/private/locales/ar.json +++ b/bigbluebutton-html5/private/locales/ar.json @@ -50,10 +50,10 @@ "app.note.title": "Ù…Ù„Ø§ØØ¸Ø§Øª مشتركة", "app.note.label": "Ù…Ù„Ø§ØØ¸Ø©", "app.note.hideNoteLabel": "Ø¥Ø®ÙØ§Ø¡ Ø§Ù„Ù…Ù„Ø§ØØ¸Ø©", + "app.note.tipLabel": "اضغط Ø§Ù„Ù…ÙØªØ§Ø Esc للتركيز علي شريط أدوات Ø§Ù„Ù…ØØ±Ø±", "app.user.activityCheck": "التØÙ‚Ù‚ من نشاط المستخدم", "app.user.activityCheck.label": "التØÙ‚Ù‚ إن كان المستخدم لا يزال ÙÙŠ الاجتماع ({0})", "app.user.activityCheck.check": "تØÙ‚Ù‚", - "app.note.tipLabel": "اضغط Ø§Ù„Ù…ÙØªØ§Ø Esc للتركيز علي شريط أدوات Ø§Ù„Ù…ØØ±Ø±", "app.userList.usersTitle": "المستخدمون", "app.userList.participantsTitle": "المشاركون", "app.userList.messagesTitle": "الرسائل", @@ -100,8 +100,8 @@ "app.userList.userOptions.hideUserList": "قائمة المستخدمين مخÙية عند المشاهدين", "app.userList.userOptions.webcamsOnlyForModerator": "لا يمكن مشاهدة كاميرات المشاهدين إلا من طر٠المشرÙين (تبعا لإعدادات القÙÙ„)", "app.userList.content.participants.options.clearedStatus": "تم مسع كل ØØ§Ù„ات المستخدم", - "app.userList.userOptions.enableCam": " ممكن عند المشاهدين إستعمال الكاميرات", - "app.userList.userOptions.enableMic": " ممكن عند المشاهدين إستعمال الميكروÙون", + "app.userList.userOptions.enableCam": "ممكن عند المشاهدين إستعمال الكاميرات", + "app.userList.userOptions.enableMic": "ممكن عند المشاهدين إستعمال الميكروÙون", "app.userList.userOptions.enablePrivChat": "الدردشة الخاصة Ù…ÙØ¹Ù„Ø©", "app.userList.userOptions.enablePubChat": "الدردشة العامة Ù…ÙØ¹Ù„Ø©", "app.userList.userOptions.enableNote": "تم تمكين Ø§Ù„Ù…Ù„Ø§ØØ¸Ø§Øª المشتركة", @@ -122,8 +122,6 @@ "app.meeting.meetingTimeRemaining": "الوقت المتبقي للاجتماع: {0}", "app.meeting.meetingTimeHasEnded": "انتهى الوقت. سيتم إغلاق الاجتماع قريبًا", "app.meeting.endedMessage": "سيتم اعادة توجيهك الى الشاشة الرئيسية", - "app.meeting.alertMeetingEndsUnderOneMinute": "يغلق الاجتماع ÙÙŠ غضون دقيقة.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "تغلق Ø§Ù„ØºØ±ÙØ© Ø§Ù„Ù…ÙØ±Ù‚Ø© ÙÙŠ غضون دقيقة.", "app.presentation.hide": "Ø¥ØºÙØ§Ø¡ العرض", "app.presentation.notificationLabel": "العرض Ø§Ù„ØØ§Ù„ÙŠ", "app.presentation.slideContent": "Ù…ØØªÙˆÙ‰ Ø§Ù„Ø´Ø±ÙŠØØ©", @@ -260,7 +258,6 @@ "app.leaveConfirmation.confirmLabel": "غادر", "app.leaveConfirmation.confirmDesc": "تسجيل خروجك من الاجتماع", "app.endMeeting.title": "إنهاء الاجتماع", - "app.endMeeting.description": "هل أنت متأكد من إنهاء الاجتماع Ø§Ù„ØØ§Ù„ي؟", "app.endMeeting.yesLabel": "نعم", "app.endMeeting.noLabel": "لا", "app.about.title": "ØÙˆÙ„", @@ -281,10 +278,6 @@ "app.screenshare.screenShareLabel" : "مشاركة Ø³Ø·Ø Ø§Ù„ÙƒØªØ¨", "app.submenu.application.applicationSectionTitle": "تطبيق", "app.submenu.application.animationsLabel": "Ø§Ù„ØªØØ±ÙŠÙƒØ§Øª", - "app.submenu.application.audioAlertLabel": "تنبيهات صوتية للدردشة", - "app.submenu.application.pushAlertLabel": "التنبيهات المنبثقة عن الدردشة", - "app.submenu.application.userJoinAudioAlertLabel": "تنبيهات صوتية لانضمام المستخدم", - "app.submenu.application.userJoinPushAlertLabel": "التنبيهات المنبثقة لانضمام المستخدم", "app.submenu.application.fontSizeControlLabel": "ØØ¬Ù… الخط", "app.submenu.application.increaseFontBtnLabel": "زيادة ØØ¬Ù… خط التطبيق", "app.submenu.application.decreaseFontBtnLabel": "تقليص ØØ¬Ù… خط التطبيق", @@ -415,7 +408,7 @@ "app.audioModal.playAudio": "تشغيل الصوت", "app.audioModal.playAudio.arialabel" : "تشغيل الصوت", "app.audioDial.tipIndicator": "Ù†ØµÙŠØØ©", - "app.audioDial.tipMessage": "اضغط على \"0\" ÙÙŠ هاتÙÙƒ لكتم أو Ù„ØªÙØ¹ÙŠÙ„ الصوت Ø¨Ù†ÙØ³Ùƒ.", + "app.audioDial.tipMessage": "اضغط على '0' ÙÙŠ هاتÙÙƒ لكتم أو Ù„ØªÙØ¹ÙŠÙ„ الصوت Ø¨Ù†ÙØ³Ùƒ.", "app.audioModal.connecting": "اتصال", "app.audioModal.connectingEchoTest": "الاتصال باختبار الصدى", "app.audioManager.joinedAudio": "لقد انضممت إلى المؤتمر الصوتي", @@ -566,19 +559,6 @@ "app.video.videoMenuDesc": "Ø§ÙØªØ القائمة المنسدلة لقائمة مقاطع الÙيديو", "app.video.chromeExtensionError": "يجب عليك التثبيت", "app.video.chromeExtensionErrorLink": "هذا ملØÙ‚ كروم", - "app.video.stats.title": "Ø¥ØØµØ§Ø¦ÙŠØ§Øª الاتصال", - "app.video.stats.packetsReceived": "تلقى Ø§Ù„ØØ²Ù…", - "app.video.stats.packetsSent": "Ø§Ù„ØØ²Ù… المرسلة", - "app.video.stats.packetsLost": "Ø§Ù„ØØ²Ù… المÙقودة", - "app.video.stats.bitrate": "معدل البت", - "app.video.stats.lostPercentage": "مجموع النسبة المÙقودة", - "app.video.stats.lostRecentPercentage": "النسبة المÙقودة الأخيرة", - "app.video.stats.dimensions": "الأبعاد", - "app.video.stats.codec": "التشÙير", - "app.video.stats.decodeDelay": "تأخير ÙÙƒ التشÙير", - "app.video.stats.rtt": "مدة الذهاب والإياب", - "app.video.stats.encodeUsagePercent": "تشÙير الاستخدام", - "app.video.stats.currentDelay": "التأخير Ø§Ù„ØØ§Ù„ÙŠ", "app.fullscreenButton.label": "جعل {0} على الشاشة الكاملة", "app.deskshare.iceConnectionStateError": "ÙØ´Ù„ الاتصال عند مشاركة الشاشة (خطأ ICE 1108)", "app.sfu.mediaServerConnectionError2000": "غير قادر على الاتصال بخادم الوسائط (خطأ 2000)", @@ -591,7 +571,6 @@ "app.sfu.invalidSdp2202":"قام العميل بإنشاء طلب وسائط غير ØµØ§Ù„Ø (خطأ SDP 2202)", "app.sfu.noAvailableCodec2203": "تعذر على الخادم العثور على برنامج ترميز مناسب (خطأ 2203)", "app.meeting.endNotification.ok.label": "مواÙÙ‚", - "app.whiteboard.annotations.poll": "تم نشر نتائج التصويت", "app.whiteboard.toolbar.tools": "أدوات", "app.whiteboard.toolbar.tools.hand": "Ù„ÙˆØØ©", "app.whiteboard.toolbar.tools.pencil": "قلم", @@ -672,7 +651,6 @@ "app.externalVideo.autoPlayWarning": "تشغيل الÙيديو لتمكين مزامنة الوسائط", "app.network.connection.effective.slow": "Ù†Ù„Ø§ØØ¸ مشاكل ÙÙŠ الاتصال", "app.network.connection.effective.slow.help": "المزيد من المعلومات", - "app.externalVideo.noteLabel": "Ù…Ù„Ø§ØØ¸Ø©: لن تظهر مقاطع الÙيديو الخارجية المشتركة ÙÙŠ التسجيل. يتم دعم YouTube Ùˆ Vimeo Ùˆ Instructure Media Ùˆ Twitch وعناوين URL DailyMotion.", "app.actionsBar.actionsDropdown.shareExternalVideo": "مشاركة Ùيديو خارجي", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "إيقا٠مشاركة الÙيديو الخارجي", "app.iOSWarning.label": "يرجى الترقية إلى iOS 12.2 أو اعلى", diff --git a/bigbluebutton-html5/private/locales/az.json b/bigbluebutton-html5/private/locales/az.json index 494874f2c4dab871ef4dca4edc69177f330d9bee..613c5b104d745b59835f6d3f28ad16791f3390f1 100644 --- a/bigbluebutton-html5/private/locales/az.json +++ b/bigbluebutton-html5/private/locales/az.json @@ -50,10 +50,10 @@ "app.note.title": "QeydlÉ™ri bölüş", "app.note.label": "Qeyd", "app.note.hideNoteLabel": "QeydlÉ™ri gözlÉ™t", + "app.note.tipLabel": "DiqqÉ™ti redaktora yönlÉ™ndirmÉ™k üçün ESC düymÉ™sini sıxın.", "app.user.activityCheck": "İstifadəçi fÉ™aliyyÉ™tini yoxla", "app.user.activityCheck.label": "({0}) istifadəçilÉ™rin sessiyada olduÄŸunu yoxlayın", "app.user.activityCheck.check": "Yoxla", - "app.note.tipLabel": "DiqqÉ™ti redaktora yönlÉ™ndirmÉ™k üçün ESC düymÉ™sini sıxın.", "app.userList.usersTitle": "İstifadəçilÉ™r", "app.userList.participantsTitle": "İştirakçılar", "app.userList.messagesTitle": "Mesaj", @@ -121,8 +121,6 @@ "app.meeting.meetingTimeRemaining": "Görüş vaxtına {0} qalıb", "app.meeting.meetingTimeHasEnded": "Vaxt bitdi. Görüş tezliklÉ™ baÄŸlanacaq.", "app.meeting.endedMessage": "Sizi ana sÉ™hifÉ™yÉ™ yönlÉ™ndiririk.", - "app.meeting.alertMeetingEndsUnderOneMinute": "Görüş bir dÉ™qiqÉ™ içindÉ™ bitÉ™cÉ™k.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "FasilÉ™ bir dÉ™qiqÉ™ içindÉ™ bitÉ™cÉ™k.", "app.presentation.hide": "TÉ™qdimatı gizlÉ™t", "app.presentation.notificationLabel": "Mövcud tÉ™qdimat", "app.presentation.slideContent": "Slayd mÉ™zmunu", @@ -194,7 +192,7 @@ "app.poll.quickPollInstruction": "SorÄŸu baÅŸlatmaq üçün aÅŸağıdakı seçimi seçin", "app.poll.customPollLabel": "FÉ™rdi sorÄŸu ", "app.poll.startCustomLabel": "FÉ™rdi sorÄŸunu baÅŸla", - "app.poll.activePollInstruction": "SorÄŸuya cavabları görmÉ™k üçün bu paneli açıq saxlayın. Hazır olduqda, sorÄŸu nÉ™ticÉ™lÉ™rini açıqlamaq vÉ™ sorÄŸunu bitirmÉ™k üçün \"SorÄŸu nÉ™ticÉ™lÉ™rini açıqla\" düymÉ™sini seç.", + "app.poll.activePollInstruction": "SorÄŸuya cavabları görmÉ™k üçün bu paneli açıq saxlayın. Hazır olduqda, sorÄŸu nÉ™ticÉ™lÉ™rini açıqlamaq vÉ™ sorÄŸunu bitirmÉ™k üçün 'SorÄŸu nÉ™ticÉ™lÉ™rini açıqla' düymÉ™sini seç.", "app.poll.publishLabel": "SorÄŸu nÉ™ticÉ™lÉ™rini açıqla", "app.poll.backLabel": "SorÄŸu seçimlÉ™rinÉ™ qayıt", "app.poll.closeLabel": "BaÄŸla", @@ -259,7 +257,6 @@ "app.leaveConfirmation.confirmLabel": "TÉ™rk et", "app.leaveConfirmation.confirmDesc": "Sizi görüşdÉ™n çıxarır", "app.endMeeting.title": "Görüşü bitir", - "app.endMeeting.description": "Bu sessiyanı biirmÉ™yÉ™ É™minsiniz?", "app.endMeeting.yesLabel": "BÉ™li", "app.endMeeting.noLabel": "Yox", "app.about.title": "Haqqında", @@ -280,10 +277,6 @@ "app.screenshare.screenShareLabel" : "Ekranı paÅŸla", "app.submenu.application.applicationSectionTitle": "Proqram", "app.submenu.application.animationsLabel": "Animasiyalar", - "app.submenu.application.audioAlertLabel": "Çat üçün sÉ™s bildiriÅŸlÉ™ri", - "app.submenu.application.pushAlertLabel": "Çat üçün popup bildiriÅŸlÉ™ri", - "app.submenu.application.userJoinAudioAlertLabel": "İstifadəçi qoÅŸulması üçün sÉ™s bildiriÅŸi", - "app.submenu.application.userJoinPushAlertLabel": "İstifadəçi qoÅŸulması üçün popup bildiriÅŸi", "app.submenu.application.fontSizeControlLabel": "Fontun ölçünü", "app.submenu.application.increaseFontBtnLabel": "Proqramın font ölçüsünü artır", "app.submenu.application.decreaseFontBtnLabel": "Proqramın font ölçüsünü azalt", @@ -565,19 +558,6 @@ "app.video.videoMenuDesc": "Video menu dropdown-unu aç", "app.video.chromeExtensionError": "Siz quraÅŸdırmalısınız", "app.video.chromeExtensionErrorLink": "bu Chrome É™lavÉ™si", - "app.video.stats.title": "QoÅŸulma statistikası", - "app.video.stats.packetsReceived": "QÉ™bul olunan paketlÉ™r", - "app.video.stats.packetsSent": "GöndÉ™rilmiÅŸ paketlÉ™r", - "app.video.stats.packetsLost": "İtirilmiÅŸ paketlÉ™r", - "app.video.stats.bitrate": "Bitreyt", - "app.video.stats.lostPercentage": "Total faiz itkisi", - "app.video.stats.lostRecentPercentage": "TÉ™zÉ™likcÉ™ itki faizi", - "app.video.stats.dimensions": "ÖlçülÉ™r", - "app.video.stats.codec": "Codec", - "app.video.stats.decodeDelay": "GecikmÉ™ni dekod et", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "İstidÉ™ni enkod et", - "app.video.stats.currentDelay": "Mövcud gecikmÉ™", "app.fullscreenButton.label": "{0} tam ekran et", "app.deskshare.iceConnectionStateError": "Ekranı paylaÅŸarkÉ™n baÄŸlantı kÉ™sildi (ICE xÉ™ta 1108)", "app.sfu.mediaServerConnectionError2000": "Media serverÉ™ qoÅŸulmaq alınmadı (xÉ™ta 2000)", @@ -590,7 +570,6 @@ "app.sfu.invalidSdp2202":"Klient etibarsız media sorÄŸu generasiya etdi (SDP xÉ™ta 2202)", "app.sfu.noAvailableCodec2203": "Server uyÄŸun codec tapa bilmÉ™di (xÉ™ta 2203)", "app.meeting.endNotification.ok.label": "OK", - "app.whiteboard.annotations.poll": "SorÄŸu nÉ™ticÉ™lÉ™ri açıqlandı", "app.whiteboard.toolbar.tools": "AlÉ™tlÉ™r", "app.whiteboard.toolbar.tools.hand": "Æl", "app.whiteboard.toolbar.tools.pencil": "QÉ™lÉ™m", @@ -671,7 +650,6 @@ "app.externalVideo.autoPlayWarning": "Media sinkronizasiyasını aktiv etmÉ™k üçün videonu iÅŸÉ™ sal", "app.network.connection.effective.slow": "BaÄŸlantı ilÉ™ baÄŸlı problemlÉ™r aÅŸkarlanmaqdadır.", "app.network.connection.effective.slow.help": "Daha çox mÉ™lumat", - "app.externalVideo.noteLabel": "Qeyd: Shared external videos will not appear in the recording. YouTube, Vimeo, Instructure Media, Twitch and Daily Motion URLs are supported.", "app.actionsBar.actionsDropdown.shareExternalVideo": "Share an external video", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Stop sharing external video", "app.iOSWarning.label": "ZÉ™hmÉ™t olmasa iOS 12.2 vÉ™ ya daha yuxarı versiyanı yüklÉ™", diff --git a/bigbluebutton-html5/private/locales/bg_BG.json b/bigbluebutton-html5/private/locales/bg_BG.json index d7a129049a8e504802a0c42824a586aea40b858f..fd65aeed9f763439a7f0b5d6993503fb95698776 100644 --- a/bigbluebutton-html5/private/locales/bg_BG.json +++ b/bigbluebutton-html5/private/locales/bg_BG.json @@ -18,6 +18,7 @@ "app.chat.dropdown.save": "Запиши", "app.chat.label": "Чат", "app.chat.offline": "Offline", + "app.chat.pollResult": "Резултати от проучването", "app.chat.emptyLogLabel": "ИÑториÑта на чата е празна", "app.chat.clearPublicChatMessage": "ИÑториÑта на разговора беше изчиÑтена от модератора", "app.chat.multi.typing": "Пишат нÑколко потребителÑ", @@ -50,15 +51,15 @@ "app.note.title": "Споделени бележки", "app.note.label": "Бележка", "app.note.hideNoteLabel": "Скрий бележката", + "app.note.tipLabel": "ÐатиÑнете Esc за връщане в редактора", "app.user.activityCheck": "Проверка на потребителÑката активноÑÑ‚", "app.user.activityCheck.label": "Проверка дали Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ Ðµ още в Ñрещата ({0})", "app.user.activityCheck.check": "Провери", - "app.note.tipLabel": "ÐатиÑнете Esc за връщане в редактора", "app.userList.usersTitle": "Потребители", "app.userList.participantsTitle": "УчаÑтници", "app.userList.messagesTitle": "СъобщениÑ", "app.userList.notesTitle": "Бележки", - "app.userList.notesListItem.unreadContent": "Ð’ раздел \"Споделени бележки\" има ново Ñъдържание", + "app.userList.notesListItem.unreadContent": "Ð’ раздел 'Споделени бележки' има ново Ñъдържание", "app.userList.captionsTitle": "Субтитри", "app.userList.presenter": "Лектор", "app.userList.you": "Вие", @@ -73,6 +74,8 @@ "app.userList.menu.chat.label": "Започни личен чат", "app.userList.menu.clearStatus.label": "ИзчиÑти ÑтатуÑа", "app.userList.menu.removeUser.label": "Изключи потребителÑ", + "app.userList.menu.removeConfirmation.label": "Изключи Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ ({0})", + "app.userlist.menu.removeConfirmation.desc": "Предотврати повторно влизане на потребителÑ", "app.userList.menu.muteUserAudio.label": "Заглуши", "app.userList.menu.unmuteUserAudio.label": "включи микрофона", "app.userList.userAriaLabel": "{0} {1} {2} Ð¡Ñ‚Ð°Ñ‚ÑƒÑ {3}", @@ -93,6 +96,8 @@ "app.userList.userOptions.unmuteAllDesc": "Премахва заглушаването на Ñрещата", "app.userList.userOptions.lockViewersLabel": "Задай ограничение", "app.userList.userOptions.lockViewersDesc": "Заключете определени функционалноÑти за учаÑтниците в Ñрещата", + "app.userList.userOptions.connectionStatusLabel": "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð½Ð° връзката", + "app.userList.userOptions.connectionStatusDesc": "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð½Ð° връзката на потребителите", "app.userList.userOptions.disableCam": "Web камерите на учаÑтниците Ñа изключени", "app.userList.userOptions.disableMic": "Микрофоните на учаÑтниците Ñа изключени", "app.userList.userOptions.disablePrivChat": "Ð›Ð¸Ñ‡Ð½Ð¸Ñ Ñ‡Ð°Ñ‚ е изключен", @@ -124,8 +129,8 @@ "app.meeting.meetingTimeRemaining": "ОÑтаващо време до ÐºÑ€Ð°Ñ Ð½Ð° Ñрещата: {0}", "app.meeting.meetingTimeHasEnded": "Времето изтече: Скоро Ñрещата ще приключи", "app.meeting.endedMessage": "Ще бъдете пренаÑочени към Ð½Ð°Ñ‡Ð°Ð»Ð½Ð¸Ñ ÐµÐºÑ€Ð°Ð½", - "app.meeting.alertMeetingEndsUnderOneMinute": "Срещата ще приклюочи Ñлед минута", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Екипнатат ÑÑ‚Ð°Ñ Ñ‰Ðµ Ñе затвори Ñлед минута", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "Срещата ще приключи до една минута", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "Срещата ще приключи до {0} минути", "app.presentation.hide": "Скрий презентациÑта", "app.presentation.notificationLabel": "Текуща презентациÑ", "app.presentation.slideContent": "Съдържание на Ñлайда", @@ -172,6 +177,9 @@ "app.presentationUploder.rejectedError": "ИзбраниÑ/те файл(ове) бÑха отхвърлени. ÐœÐ¾Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐµÑ‚Ðµ типа на файла(овете)", "app.presentationUploder.upload.progress": "Качване ({0}%)", "app.presentationUploder.upload.413": "Файла е прекалено голÑм. ÐœÐ¾Ð»Ñ Ñ€Ð°Ð·Ð´ÐµÐ»ÐµÑ‚Ðµ го на нÑколко файла", + "app.presentationUploder.upload.408": "Времето на заÑвката за upload токен изтече", + "app.presentationUploder.upload.404": "404: Ðевалиден токен за upload", + "app.presentationUploder.upload.401": "ÐеуÑпешна заÑвка за презентациÑ", "app.presentationUploder.conversion.conversionProcessingSlides": "Обработка на Ñтраница {0} от {1}", "app.presentationUploder.conversion.genericConversionStatus": "Конвертиране на файла ...", "app.presentationUploder.conversion.generatingThumbnail": "Генериране на thumbnails ...", @@ -197,7 +205,7 @@ "app.poll.quickPollInstruction": "Изберете вариантите по-долу за да Ñтартирате Вашето проучване", "app.poll.customPollLabel": "Създайте проучване", "app.poll.startCustomLabel": "Стартирайте проучването", - "app.poll.activePollInstruction": "ОÑтавете панела отворен за да виждате в реално време отговорите на Вашето проучване. Когато Ñте готови, изберете \"Публикувай резултатите\"", + "app.poll.activePollInstruction": "ОÑтавете панела отворен за да виждате в реално време отговорите на Вашето проучване. Когато Ñте готови, изберете 'Публикувай резултатите'", "app.poll.publishLabel": "Публикувай резултатите", "app.poll.backLabel": "Обратно към опциите на проучването", "app.poll.closeLabel": "Затвори", @@ -252,6 +260,7 @@ "app.navBar.settingsDropdown.endMeetingDesc": "ПрекратÑва текущата Ñреща", "app.navBar.settingsDropdown.endMeetingLabel": "Край на Ñрещата", "app.navBar.userListToggleBtnLabel": "Показва ÑпиÑъка Ñ Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ð¸", + "app.navBar.toggleUserList.ariaLabel": "Скрий/покажи потребителите и ÑъобщениÑта", "app.navBar.toggleUserList.newMessages": "with new message notification", "app.navBar.recording": "Тази ÑеÑÐ¸Ñ Ñе запиÑва", "app.navBar.recording.on": "ЗапиÑ", @@ -260,7 +269,7 @@ "app.leaveConfirmation.confirmLabel": "ÐапуÑни", "app.leaveConfirmation.confirmDesc": "ÐапуÑкане на Ñрещата", "app.endMeeting.title": "Край на Ñрещата", - "app.endMeeting.description": "Сигурни ли Ñте че иÑкате да приключите тази ÑеÑиÑ?", + "app.endMeeting.description": "Сигурни ли Ñте че желаете да приключите Ñрещата за вÑички (вÑички потребители ще бъдат разкачени)?", "app.endMeeting.yesLabel": "Да", "app.endMeeting.noLabel": "Ðе", "app.about.title": "ОтноÑно", @@ -279,10 +288,6 @@ "app.screenshare.screenShareLabel" : "СподелÑне на екрана", "app.submenu.application.applicationSectionTitle": "Приложение", "app.submenu.application.animationsLabel": "Ðнимации", - "app.submenu.application.audioAlertLabel": "Ðудио уведомление за чат", - "app.submenu.application.pushAlertLabel": "Popup уведомление за чат", - "app.submenu.application.userJoinAudioAlertLabel": "Ðудио уведомление за влÑзъл потребител", - "app.submenu.application.userJoinPushAlertLabel": "Popup уведомление за влÑзъл потребител", "app.submenu.application.fontSizeControlLabel": "Размер на шрифта", "app.submenu.application.increaseFontBtnLabel": "Увеличи размера на шрифта", "app.submenu.application.decreaseFontBtnLabel": "Ðамали размера на шрифта", @@ -290,6 +295,11 @@ "app.submenu.application.languageLabel": "Език на интерфейÑа", "app.submenu.application.languageOptionLabel": "Изберете език", "app.submenu.application.noLocaleOptionLabel": "ÐÑма активна локализациÑ", + "app.submenu.notification.SectionTitle": "СъобщениÑ", + "app.submenu.notification.Desc": "Дефинира как и за какво ще бъдете информирани", + "app.submenu.notification.audioAlertLabel": "Звукови предупреждениÑ", + "app.submenu.notification.pushAlertLabel": "Popup предупреждениÑ", + "app.submenu.notification.messagesLabel": "Чат Ñъобщение", "app.submenu.audio.micSourceLabel": "Микрофон - източник", "app.submenu.audio.speakerSourceLabel": "Говорители - източник", "app.submenu.audio.streamVolumeLabel": "Сила на звука", @@ -329,10 +339,12 @@ "app.actionsBar.actionsDropdown.desktopShareDesc": "Сподели екрана Ñ Ð¾Ñтаналите", "app.actionsBar.actionsDropdown.stopDesktopShareDesc": "Спри ÑподелÑнето Ñ", "app.actionsBar.actionsDropdown.pollBtnLabel": "Стартирай проучване", + "app.actionsBar.actionsDropdown.pollBtnDesc": "Показва/Ñкрива панела на проучването", "app.actionsBar.actionsDropdown.saveUserNames": "Свали ÑпиÑък", "app.actionsBar.actionsDropdown.createBreakoutRoom": "Създай екипни Ñтаи", "app.actionsBar.actionsDropdown.createBreakoutRoomDesc": "Създай екипи за да разделиш текущата Ñреща", "app.actionsBar.actionsDropdown.captionsLabel": "Пиши Ñубтитри", + "app.actionsBar.actionsDropdown.captionsDesc": "Показва/Ñкрива панела на Ñубтитрите", "app.actionsBar.actionsDropdown.takePresenter": "Стани презентатор", "app.actionsBar.actionsDropdown.takePresenterDesc": "Правиш Ñе презентатор", "app.actionsBar.emojiMenu.statusTriggerLabel": "СтатуÑ", @@ -365,7 +377,10 @@ "app.audioNotification.audioFailedError1005": "Обаждането приключи неочаквано (грешка 1005)", "app.audioNotification.audioFailedError1007": "Проблем Ñ Ð²Ñ€ÑŠÐ·ÐºÐ°Ñ‚Ð° (ICE грешка 1007)", "app.audioNotification.audioFailedError1008": "Грешка при прехвърлÑнето (грешка 1008)", + "app.audioNotification.audioFailedError1009": "Ðе е намерена Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° STUN/TURN Ñървър (грешка 1009)", "app.audioNotification.audioFailedError1010": "Времето за договарÑне на връзката изтече (грешка 1010)", + "app.audioNotification.audioFailedError1011": "Времето за връзка изтече (ICE грешка 1011)", + "app.audioNotification.audioFailedError1012": "Връзката е прекратена (ICE грешка 1012)", "app.audioNotification.audioFailedMessage": "Проблем Ñ Ð¾ÑъщеÑтвÑването на аудио връзка", "app.audioNotification.closeLabel": "Затвори", "app.audioNotificaion.reconnectingAsListenOnly": "Използването на микрофони от учаÑтниците е забранено, в момента Ñте Ñамо Ñлушател", @@ -395,7 +410,7 @@ "app.audioModal.echoTestTitle": "Това е личен ехо теÑÑ‚. Кажете нÑколко думи. Чувате ли глаÑа Ñи?", "app.audioModal.settingsTitle": "Променете аудио наÑтройките ", "app.audioModal.helpTitle": "Има проблем Ñ Ð²Ð°ÑˆÐ¸Ñ‚Ðµ медиини уÑтройÑтва", - "app.audioModal.helpText": "Дадохте ли разрешение за доÑтъп до Ð’Ð°ÑˆÐ¸Ñ Ð¼Ð¸ÐºÑ€Ð¾Ñ„Ð¾Ð½?\nОбърнете внимание на прозореца който ще Ñе поÑви когато опитате да включите аудиото, ще Ви бъде поиÑкано разрешение за доÑтъп до вашите камера/микрофон, Ð¼Ð¾Ð»Ñ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÑ‚Ðµ ги за да можете да учаÑтвате в аудио конференциÑта. Опитайте да промените разрешениÑта за камера/микрофон от наÑтройките на браузъра", + "app.audioModal.helpText": "Дадохте ли разрешение за доÑтъп до Ð’Ð°ÑˆÐ¸Ñ Ð¼Ð¸ÐºÑ€Ð¾Ñ„Ð¾Ð½? Обърнете внимание на прозореца който ще Ñе поÑви когато опитате да включите аудиото, ще Ви бъде поиÑкано разрешение за доÑтъп до вашите камера/микрофон, Ð¼Ð¾Ð»Ñ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÑ‚Ðµ ги за да можете да учаÑтвате в аудио конференциÑта. Опитайте да промените разрешениÑта за камера/микрофон от наÑтройките на браузъра", "app.audioModal.help.noSSL": "Тази Ñтраница е неÑигурна. За бъде разрешен доÑтъпа до микрофона Ñтраницата трÑбва да поддържа HTTPS. ÐœÐ¾Ð»Ñ Ñвържете Ñе ÑÑŠÑ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтратора на Ñървъра.", "app.audioModal.help.macNotAllowed": "Изглежда че вашите MAC System Preferences блокират доÑтъпа до микрофона ви. Отворете System Preferences>Security&Privacy>Microfone, и проверете дали е избран браузъра който ползвате.", "app.audioModal.audioDialTitle": "Влезте чрез Ð²Ð°ÑˆÐ¸Ñ Ñ‚ÐµÐ»ÐµÑ„Ð¾Ð½", @@ -414,6 +429,8 @@ "app.audioManager.reconnectingAudio": "Опит за възтановÑване на аудиото", "app.audioManager.genericError": "Грешка: Възникна грешка, Ð¼Ð¾Ð»Ñ Ð¾Ð¿Ð¸Ñ‚Ð°Ð¹Ñ‚Ðµ отнова", "app.audioManager.connectionError": "Грешка: Проблем Ñ Ð²Ñ€ÑŠÐ·ÐºÐ°Ñ‚Ð°", + "app.audioManager.requestTimeout": "Грешка: Времето на заÑвката изтече", + "app.audioManager.invalidTarget": "Грешка: Ðевалидна заÑвка", "app.audioManager.mediaError": "Грешка: Има проблем Ñ Ð´Ð¾Ñъпа до вашите камера/микрофон", "app.audio.joinAudio": "Включи аудиото", "app.audio.leaveAudio": "Изключи аудиото", @@ -437,8 +454,10 @@ "app.meeting.logout.ejectedFromMeeting": "Вие бÑхте изключен от Ñрещата", "app.meeting.logout.validateTokenFailedEjectReason": "Грешка при проверка на Ð°ÑƒÑ‚Ð¾Ñ€Ð¸Ð·Ð¸Ñ€Ð°Ñ‰Ð¸Ñ Ñ‚Ð¾ÑƒÐºÑŠÐ½", "app.meeting.logout.userInactivityEjectReason": "ÐŸÐ¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ Ð½Ðµ е активен отдавна", + "app.meeting-ended.rating.legendLabel": "Обратна връзка", "app.meeting-ended.rating.starLabel": "Звезда", "app.modal.close": "Затвори", + "app.modal.close.description": "ÐžÑ‚Ñ…Ð²ÑŠÑ€Ð»Ñ Ð¿Ñ€Ð¾Ð¼ÐµÐ½Ð¸Ñ‚Ðµ и Ð·Ð°Ñ‚Ð²Ð°Ñ€Ñ Ð¼Ð¾Ð´Ð°Ð»Ð½Ð¸Ñ Ð¿Ñ€Ð¾Ð·Ð¾Ñ€ÐµÑ†", "app.modal.confirm": "Завършено", "app.modal.newTab": "(Ð¾Ñ‚Ð²Ð°Ñ€Ñ Ð½Ð¾Ð² таб)", "app.modal.confirm.description": "Запази промÑната и затвори избора", @@ -479,6 +498,7 @@ "app.notification.recordingPaused": "Тази ÑеÑÐ¸Ñ Ð²ÐµÑ‡Ðµ не Ñе запиÑва", "app.notification.recordingAriaLabel": "Време на запиÑ", "app.notification.userJoinPushAlert": "{0} Ñе приÑъедини към ÑеÑиÑта", + "app.submenu.notification.raiseHandLabel": "Вдигни ръка", "app.shortcut-help.title": "Клавишни комбинации", "app.shortcut-help.comboLabel": "КомбинациÑ", "app.shortcut-help.functionLabel": "ФункциÑ", @@ -510,6 +530,11 @@ "app.lock-viewers.button.cancel": "Отказ", "app.lock-viewers.locked": "Забранено", "app.lock-viewers.unlocked": "Разрешено", + "app.connection-status.title": "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð½Ð° връзката", + "app.connection-status.description": "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð½Ð° връзката на потребителите", + "app.connection-status.empty": "ДоÑега не Ñа забелÑзани проблеми Ñ Ð²Ñ€ÑŠÐ·ÐºÐ°Ñ‚Ð°", + "app.connection-status.more": "още", + "app.connection-status.offline": "офлайн", "app.recording.startTitle": "Стартиране на запиÑа", "app.recording.stopTitle": "Пауза на запиÑа", "app.recording.resumeTitle": "Продължи запиÑа", @@ -521,6 +546,8 @@ "app.videoPreview.closeLabel": "Затвори", "app.videoPreview.findingWebcamsLabel": "ТърÑене на камери", "app.videoPreview.startSharingLabel": "Започване на ÑподелÑнето", + "app.videoPreview.stopSharingLabel": "Спри ÑподелÑнето", + "app.videoPreview.sharedCameraLabel": "Тази камера вече е Ñподелена", "app.videoPreview.webcamOptionLabel": "Избери камера", "app.videoPreview.webcamPreviewLabel": "Камера преглед", "app.videoPreview.webcamSettingsTitle": "ÐаÑтройки на камерата", @@ -535,10 +562,12 @@ "app.video.notAllowed": "ЛипÑва разрешение за ÑподелÑне на камерата, Ð¼Ð¾Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐµÑ‚Ðµ разрешениÑта на браузъра", "app.video.notSupportedError": "Може да Ñе ÑÐ¿Ð¾Ð´ÐµÐ»Ñ Ð²Ð¸Ð´ÐµÐ¾ от камерата Ñамо от Ñигурни източници, проверете валидноÑтта на SSL Ñертификата", "app.video.notReadableError": "ÐÑма доÑтъп до видеото от камерата. ÐœÐ¾Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐµÑ‚Ðµ дали друго приложение не Ñ Ð¿Ð¾Ð»Ð·Ð²Ð°.", + "app.video.mediaFlowTimeout1020": "МедиÑта не може да доÑтигне до Ñървъра (грешка 1020)", "app.video.suggestWebcamLockReason": "(това ще подобри ÑтабилноÑтта на Ñрещата)", "app.video.enable": "Разреши", "app.video.cancel": "Отказ", "app.video.swapCam": "Размени", + "app.video.swapCamDesc": "Размени уеб камерите", "app.video.videoLocked": "СподелÑнето на камерата забранено", "app.video.videoButtonDesc": "СподелÑне на камерата", "app.video.videoMenu": "Видео меню", @@ -546,16 +575,6 @@ "app.video.videoMenuDesc": "Отвори падащото видео меню", "app.video.chromeExtensionError": "ТрÑбва да инÑталирате", "app.video.chromeExtensionErrorLink": "това Chrome разширение", - "app.video.stats.title": "СтатиÑтика на връзката", - "app.video.stats.packetsReceived": "Приети пакети", - "app.video.stats.packetsSent": "Изпратени пакети", - "app.video.stats.packetsLost": "Загуба на пакети", - "app.video.stats.bitrate": "Битрейт", - "app.video.stats.lostPercentage": "Общ % на загубите", - "app.video.stats.lostRecentPercentage": "Скорошен % на загубите", - "app.video.stats.dimensions": "Размери", - "app.video.stats.codec": "Кодек", - "app.video.stats.currentDelay": "Текущо забавÑне", "app.fullscreenButton.label": "Покажи {0} на цÑл екран", "app.deskshare.iceConnectionStateError": "Проблем Ñ Ð²Ñ€ÑŠÐ·ÐºÐ°Ñ‚Ð° при ÑподелÑне на екрана (ICE грешка 1108)", "app.sfu.mediaServerConnectionError2000": "Връзката Ñ‚Ñ Ð¼ÐµÐ´Ð¸Ñ Ñървъра невъзможна (Грешка 2000)", @@ -567,7 +586,6 @@ "app.sfu.invalidSdp2202":"Клиента генерира невалидна Ð¼ÐµÐ´Ð¸Ñ Ð·Ð°Ñвка (SDP грешка 2202)", "app.sfu.noAvailableCodec2203": "Сървъра на може да намери подходÑщ кодек (грешка 2203)", "app.meeting.endNotification.ok.label": "OK", - "app.whiteboard.annotations.poll": "Резултатите от проучването Ñа публикувани", "app.whiteboard.toolbar.tools": "ИнÑтрументи", "app.whiteboard.toolbar.tools.hand": "Показалец", "app.whiteboard.toolbar.tools.pencil": "Молив", @@ -642,12 +660,12 @@ "app.externalVideo.urlError": "Този видеоизточник не Ñе поддържа.", "app.externalVideo.close": "Затвори", "app.externalVideo.autoPlayWarning": "Щракнете вълхо видеото за да го пуÑнете", + "app.network.connection.effective.slow": "Съобщаваме за проблеми Ñ Ð²Ñ€ÑŠÐ·ÐºÐ°Ñ‚Ð°", "app.network.connection.effective.slow.help": "Повече информациÑ", - "app.externalVideo.noteLabel": "Бележка: Външното видео не Ñе включва в запиÑа.\nПоддържани източници: YouYube, Vimeo,  Instructure Media, Twitch and Daily Motion URLs are supported.", "app.actionsBar.actionsDropdown.shareExternalVideo": "Сподели външно видео", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Спри ÑподелÑнето на видео", "app.iOSWarning.label": "ÐœÐ¾Ð»Ñ Ð¾Ð±Ð½Ð¾Ð²ÐµÑ‚Ðµ до iOS 12.2 или по-виÑока", - "app.legacy.unsupportedBrowser": "Вие използвате браузър който не Ñе поддържа.\nЗа пълна функционалноÑÑ‚, Ð¼Ð¾Ð»Ñ Ð¸Ð·Ð¿Ð¾Ð»Ð·Ð²Ð°Ð¹Ñ‚Ðµ {0} или {1}", + "app.legacy.unsupportedBrowser": "Вие използвате браузър който не Ñе поддържа.За пълна функционалноÑÑ‚, Ð¼Ð¾Ð»Ñ Ð¸Ð·Ð¿Ð¾Ð»Ð·Ð²Ð°Ð¹Ñ‚Ðµ {0} или {1}", "app.legacy.upgradeBrowser": "Вие използвате Ñтара верÑÐ¸Ñ Ð½Ð° браузър който Ñе поддържа. За поÑтигане на пълна функционалнаÑÑ‚, Ð¼Ð¾Ð»Ñ Ð¾Ð±Ð½Ð¾Ð²ÐµÑ‚Ðµ браузъра Ñи.", "app.legacy.criosBrowser": "За пълна функционалноÑÑ‚ под iOS Ð¼Ð¾Ð»Ñ Ð¸Ð·Ð¿Ð¾Ð»Ð·Ð²Ð°Ð¹Ñ‚Ðµ Safari" diff --git a/bigbluebutton-html5/private/locales/ca.json b/bigbluebutton-html5/private/locales/ca.json index e734249c226b07ae2c99e9858cabf2c0c340a9c8..465a43ebdb137676dbfa7e89d616826f92657d57 100644 --- a/bigbluebutton-html5/private/locales/ca.json +++ b/bigbluebutton-html5/private/locales/ca.json @@ -50,10 +50,10 @@ "app.note.title": "Notes compartides", "app.note.label": "Nota", "app.note.hideNoteLabel": "Amaga nota", + "app.note.tipLabel": "Prem Esc per a centrar la barra d'edició", "app.user.activityCheck": "Revisió de l'activitat d'usuari", "app.user.activityCheck.label": "Comprova si l'usuari encara està a la reunió ({0})", "app.user.activityCheck.check": "Comprova", - "app.note.tipLabel": "Prem Esc per a centrar la barra d'edició", "app.userList.usersTitle": "Usuaris", "app.userList.participantsTitle": "Participants", "app.userList.messagesTitle": "Missatges", @@ -126,8 +126,6 @@ "app.meeting.meetingTimeRemaining": "Temps restant de la reunió: {0}", "app.meeting.meetingTimeHasEnded": "Temps finalitzat. La reunió es tancarà aviat", "app.meeting.endedMessage": "Sereu redirigit/da a la pantalla d'inici", - "app.meeting.alertMeetingEndsUnderOneMinute": "La reunió es tancará en un minut", - "app.meeting.alertBreakoutEndsUnderOneMinute": "El temps s'acabarà en un minut", "app.presentation.hide": "Amaga la presentaci", "app.presentation.notificationLabel": "Presentació actual", "app.presentation.slideContent": "Contingut de la diapositiva", @@ -267,7 +265,6 @@ "app.leaveConfirmation.confirmLabel": "Abandona", "app.leaveConfirmation.confirmDesc": "Desconnecta de la reunió", "app.endMeeting.title": "Finalitza la reunió", - "app.endMeeting.description": "Està segur/a que vol finalitzar aquesta sessió?", "app.endMeeting.yesLabel": "SÃ", "app.endMeeting.noLabel": "No", "app.about.title": "Sobre...", @@ -288,10 +285,6 @@ "app.screenshare.screenShareLabel" : "Comparteix pantalla", "app.submenu.application.applicationSectionTitle": "Aplicació", "app.submenu.application.animationsLabel": "Animació", - "app.submenu.application.audioAlertLabel": "Alertes d'audio per al xat", - "app.submenu.application.pushAlertLabel": "Alertes emergents per al xat", - "app.submenu.application.userJoinAudioAlertLabel": "Alertes d'audio per usuaris que es connecten de nou", - "app.submenu.application.userJoinPushAlertLabel": "Alertes emergents per a usuaris que es connecten de nou", "app.submenu.application.fontSizeControlLabel": "Mida de la font", "app.submenu.application.increaseFontBtnLabel": "Fes la mida de la lletra més gran", "app.submenu.application.decreaseFontBtnLabel": "Fes la mida de la lletra més petita", @@ -573,19 +566,6 @@ "app.video.videoMenuDesc": "Obre el menú desplegable de vÃdeo", "app.video.chromeExtensionError": "Heu d'instal·lar", "app.video.chromeExtensionErrorLink": "aquesta extensió de Chrome", - "app.video.stats.title": "EstadÃstiques de connexió", - "app.video.stats.packetsReceived": "Paquets rebuts", - "app.video.stats.packetsSent": "Paquets enviats", - "app.video.stats.packetsLost": "Paquets perduts", - "app.video.stats.bitrate": "Bitrate", - "app.video.stats.lostPercentage": "Percentatge total perdut", - "app.video.stats.lostRecentPercentage": "Percentatge recent perdut.", - "app.video.stats.dimensions": "Dimensions", - "app.video.stats.codec": "Códec", - "app.video.stats.decodeDelay": "Decode delay", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Codifica l'ús", - "app.video.stats.currentDelay": "Delay actual", "app.fullscreenButton.label": "Fer {0} a pantalla completa", "app.deskshare.iceConnectionStateError": "La connexió ha fallat en compartir (ICE error 1108)", "app.sfu.mediaServerConnectionError2000": "Incapaç de connectar-se al servidor de mitjans (error 2000)", @@ -598,7 +578,6 @@ "app.sfu.invalidSdp2202":"El client ha generat una petició de mitjans no và lida (SDP error 2202)", "app.sfu.noAvailableCodec2203": "El servidor no pot trobar un còdec apropiat (error 2203)", "app.meeting.endNotification.ok.label": "D'acord", - "app.whiteboard.annotations.poll": "Els resultats de l'enquesta han estat publicats.", "app.whiteboard.toolbar.tools": "Eines", "app.whiteboard.toolbar.tools.hand": "Panell", "app.whiteboard.toolbar.tools.pencil": "Llapis", @@ -679,7 +658,6 @@ "app.externalVideo.autoPlayWarning": "Reprodueix el vÃdeo per a activar la sincronització de mitjans", "app.network.connection.effective.slow": "Estem tenint problemes de connectivitat", "app.network.connection.effective.slow.help": "Més informaci", - "app.externalVideo.noteLabel": "Nota: els vÃdeos externs compartits no apareixeran a la gravació. Els URL de YouTube, Vimeo, Instructure Media, Twitch i Daily Motion són compatibles.", "app.actionsBar.actionsDropdown.shareExternalVideo": "Comparteix un vÃdeo extern", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Deixa de compartir els vÃdeos externs", "app.iOSWarning.label": "Actualitzeu a iOS 12.2 o superior", diff --git a/bigbluebutton-html5/private/locales/cs_CZ.json b/bigbluebutton-html5/private/locales/cs_CZ.json index 87529d4edadde7600ea217f51cc9f9b4149f555b..0543199790e6678f8c1040bab02afbe977c27e1f 100644 --- a/bigbluebutton-html5/private/locales/cs_CZ.json +++ b/bigbluebutton-html5/private/locales/cs_CZ.json @@ -50,10 +50,10 @@ "app.note.title": "SdÃlené poznámky", "app.note.label": "Poznámka", "app.note.hideNoteLabel": "Schovat poznámky", + "app.note.tipLabel": "ZmáÄknÄ›te klávesu Esc k vybránà panelu nástrojů", "app.user.activityCheck": "Kontrola aktivity uživatelů", "app.user.activityCheck.label": "Kontrola je li uživatel stále pÅ™Ãtomen v meetingu ({0})", "app.user.activityCheck.check": "Kontrola", - "app.note.tipLabel": "ZmáÄknÄ›te klávesu Esc k vybránà panelu nástrojů", "app.userList.usersTitle": "Uživatelé", "app.userList.participantsTitle": "ÚÄastnÃci", "app.userList.messagesTitle": "Zprávy", @@ -124,8 +124,6 @@ "app.meeting.meetingTimeRemaining": "ÄŒas zbývajÃcà do konce setkánÃ: {0}", "app.meeting.meetingTimeHasEnded": "ÄŒas setkánà vyprÅ¡el. Setkánà bude za okamžik ukonÄeno.", "app.meeting.endedMessage": "Budete pÅ™esmÄ›rováni zpÄ›t na úvodnà obrazovku", - "app.meeting.alertMeetingEndsUnderOneMinute": "Setkánà bude ukonÄeno bÄ›hem minuty.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Vedlejšà mÃstnost bude ukonÄena bÄ›hem minuty.", "app.presentation.hide": "Skrýt prezentaci", "app.presentation.notificationLabel": "SouÄasná prezentace", "app.presentation.slideContent": "Obsah stránky prezentac", @@ -200,7 +198,7 @@ "app.poll.quickPollInstruction": "Vyberte možnost nÞe a zahajte svou anketu", "app.poll.customPollLabel": "Vlastnà anketa", "app.poll.startCustomLabel": "Zahájit vlastnà anketu", - "app.poll.activePollInstruction": "Ponechte tento panel otevÅ™ený, aby jste videli žive odpovÄ›di na vaÅ¡e hlasovánà (anketu). Až budete pÅ™ipraveni, vyberte \"Publikovat výsledky ankety\". Pro zveÅ™ejnÄ›nà výsledků a ukonÄenà hlasovánÃ.", + "app.poll.activePollInstruction": "Ponechte tento panel otevÅ™ený, aby jste videli žive odpovÄ›di na vaÅ¡e hlasovánà (anketu). Až budete pÅ™ipraveni, vyberte 'Publikovat výsledky ankety'. Pro zveÅ™ejnÄ›nà výsledků a ukonÄenà hlasovánÃ.", "app.poll.publishLabel": "Publikovat výsledky ankety", "app.poll.backLabel": "ZpÄ›t na možnosti ankety", "app.poll.closeLabel": "ZavÅ™Ãt", @@ -265,7 +263,6 @@ "app.leaveConfirmation.confirmLabel": "Opustit", "app.leaveConfirmation.confirmDesc": "OpuÅ¡tÄ›nà tohoto setkánÃ", "app.endMeeting.title": "UkonÄit setkánÃ", - "app.endMeeting.description": "Jste si jist, že chcete ukonÄit toto setkánÃ?", "app.endMeeting.yesLabel": "Ano, ukonÄit", "app.endMeeting.noLabel": "Ne", "app.about.title": "O aplikaci", @@ -286,10 +283,6 @@ "app.screenshare.screenShareLabel" : "SdÃlenà obrazovky", "app.submenu.application.applicationSectionTitle": "Aplikace", "app.submenu.application.animationsLabel": "Animace", - "app.submenu.application.audioAlertLabel": "Akustická upozornÄ›nà z chatu", - "app.submenu.application.pushAlertLabel": "Vyskakovacà upozornÄ›nà pro chat", - "app.submenu.application.userJoinAudioAlertLabel": "Zvuková upozornÄ›nà pÅ™i pÅ™ipojenà uživatele", - "app.submenu.application.userJoinPushAlertLabel": "Vyskakovacà upozornÄ›nà pÅ™i pÅ™ipojenà uživatele", "app.submenu.application.fontSizeControlLabel": "Velikost pÃsma", "app.submenu.application.increaseFontBtnLabel": "ZvÄ›tÅ¡it pÃsmo aplikace", "app.submenu.application.decreaseFontBtnLabel": "ZmenÅ¡it pÃsmo aplikace", @@ -420,7 +413,7 @@ "app.audioModal.playAudio": "PÅ™ehrát zvuk", "app.audioModal.playAudio.arialabel" : "PÅ™ehrát zvuk", "app.audioDial.tipIndicator": "Tip", - "app.audioDial.tipMessage": "ZmáÄknÄ›te \"0\" na svém telefonu pro ztlumenà se / zapnutà zvuku", + "app.audioDial.tipMessage": "ZmáÄknÄ›te '0' na svém telefonu pro ztlumenà se / zapnutà zvuku", "app.audioModal.connecting": "PÅ™ipojovánÃ", "app.audioModal.connectingEchoTest": "PÅ™ipojovánà k testu ozvÄ›ny", "app.audioManager.joinedAudio": "PÅ™ipojili jste se k audio konferenci", @@ -571,19 +564,6 @@ "app.video.videoMenuDesc": "OtevÅ™Ãt video menu", "app.video.chromeExtensionError": "MusÃte nainstalovat", "app.video.chromeExtensionErrorLink": "toto rozÅ¡ÃÅ™enà Chrome", - "app.video.stats.title": "Stav pÅ™ipojenÃ", - "app.video.stats.packetsReceived": "Paketů pÅ™ijato", - "app.video.stats.packetsSent": "Paketů odesláno", - "app.video.stats.packetsLost": "Paketů ztraceno", - "app.video.stats.bitrate": "Bitrate", - "app.video.stats.lostPercentage": "Procento ztracených", - "app.video.stats.lostRecentPercentage": "Procento naposledy ztracených", - "app.video.stats.dimensions": "RozmÄ›ry", - "app.video.stats.codec": "Kodek", - "app.video.stats.decodeDelay": "ZpoždÄ›nà pÅ™i dekódovánÃ", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Využità enkodéru", - "app.video.stats.currentDelay": "Aktuálnà zpoždÄ›nÃ", "app.fullscreenButton.label": "Nastavit {0} na celou obrazovku", "app.deskshare.iceConnectionStateError": "Chyba 1108: ICE spojenà selhalo pÅ™i sdÃlenà obrazovky", "app.sfu.mediaServerConnectionError2000": "Chyba 2000: Nelze se pÅ™ipojit k media serveru", @@ -596,7 +576,6 @@ "app.sfu.invalidSdp2202":"Chyba 2202: Client vygeneroval neplatný požadavek (SDP error)", "app.sfu.noAvailableCodec2203": "Chyba 2203: Server nemůže najÃt žádný vhodný kodek", "app.meeting.endNotification.ok.label": "OK", - "app.whiteboard.annotations.poll": "Výsledky hlasovánà byli zveÅ™ejnÄ›ny", "app.whiteboard.toolbar.tools": "Nástroje", "app.whiteboard.toolbar.tools.hand": "Posunout", "app.whiteboard.toolbar.tools.pencil": "ZvýrazňovaÄ", @@ -677,7 +656,6 @@ "app.externalVideo.autoPlayWarning": "PÅ™ehrát video k povolenà synchronizace mediÃ.", "app.network.connection.effective.slow": "Pozorujeme problémy s pÅ™ipojenÃm.", "app.network.connection.effective.slow.help": "VÃce informacÃ", - "app.externalVideo.noteLabel": "Poznámka: SdÃlené externà viideo nebude obsaženo v záznamu. Pouze videa z YouTube, Vimeo, Instructure Media, Twitch a Daily Motion jsou podporována.", "app.actionsBar.actionsDropdown.shareExternalVideo": "SdÃlet externà video", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "UkonÄit sdÃlenà externÃho videa", "app.iOSWarning.label": "ProsÃm aktualizujte na iOS 12.2 nebo novÄ›jÅ¡Ã", diff --git a/bigbluebutton-html5/private/locales/da.json b/bigbluebutton-html5/private/locales/da.json index cf5cebb1748afe9c7e19782acd39b5be8bb86031..90e6949ff3f2832435033bc18ff37ba53743d918 100644 --- a/bigbluebutton-html5/private/locales/da.json +++ b/bigbluebutton-html5/private/locales/da.json @@ -50,10 +50,10 @@ "app.note.title": "Delte noter", "app.note.label": "Note", "app.note.hideNoteLabel": "Skjul note", + "app.note.tipLabel": "Tryk pÃ¥ Esc for at fokusere pÃ¥ redigeringsværktøjslinjen", "app.user.activityCheck": "Brugeraktivitetscheck", "app.user.activityCheck.label": "Kontroller, om brugeren stadig er i møde ({0})", "app.user.activityCheck.check": "Kontroller", - "app.note.tipLabel": "Tryk pÃ¥ Esc for at fokusere pÃ¥ redigeringsværktøjslinjen", "app.userList.usersTitle": "Brugere", "app.userList.participantsTitle": "Deltagere", "app.userList.messagesTitle": "Beskeder", @@ -121,8 +121,6 @@ "app.meeting.meetingTimeRemaining": "Resterende mødetid: {0}", "app.meeting.meetingTimeHasEnded": "Tiden sluttede. Mødet afsluttes snart", "app.meeting.endedMessage": "Du videresendes tilbage til startskærmen", - "app.meeting.alertMeetingEndsUnderOneMinute": "Mødet afsluttes om et minut.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Breakout lukker om et minut.", "app.presentation.hide": "Skjul præsentation", "app.presentation.notificationLabel": "Aktuel præsentation", "app.presentation.slideContent": "Slide-indhold", @@ -255,7 +253,6 @@ "app.leaveConfirmation.confirmLabel": "Forlad", "app.leaveConfirmation.confirmDesc": "Logger dig ud af mødet", "app.endMeeting.title": "Afslut møde", - "app.endMeeting.description": "Er du sikker pÃ¥ du vil afslutte denne session?", "app.endMeeting.yesLabel": "Ja", "app.endMeeting.noLabel": "Nej", "app.about.title": "Om", @@ -276,10 +273,6 @@ "app.screenshare.screenShareLabel" : "Del skærm", "app.submenu.application.applicationSectionTitle": "Applikation", "app.submenu.application.animationsLabel": "Animationer", - "app.submenu.application.audioAlertLabel": "Lyd notifikationer for: Chat", - "app.submenu.application.pushAlertLabel": "Popup notifikationer for: Chat", - "app.submenu.application.userJoinAudioAlertLabel": "Lyd notifikationer for: Bruger deltager ", - "app.submenu.application.userJoinPushAlertLabel": "Popup notifikation for: Bruger deltager", "app.submenu.application.fontSizeControlLabel": "Skriftstørrelse", "app.submenu.application.increaseFontBtnLabel": "Forøg programmets skriftstørrelse ", "app.submenu.application.decreaseFontBtnLabel": "Formindsk programmets skriftstørrelse", @@ -336,32 +329,32 @@ "app.actionsBar.actionsDropdown.takePresenterDesc": "Tilføj dig selv som ny Presenter", "app.actionsBar.emojiMenu.statusTriggerLabel": "Sæt status", "app.actionsBar.emojiMenu.awayLabel": "Væk", - "app.actionsBar.emojiMenu.awayDesc": "Skift din status til - \"Ikke tilstede\"", + "app.actionsBar.emojiMenu.awayDesc": "Skift din status til - 'Ikke tilstede'", "app.actionsBar.emojiMenu.raiseHandLabel": "Hæv", "app.actionsBar.emojiMenu.raiseHandDesc": "Sæt hÃ¥nden i vejret, for at stille et spørgsmÃ¥l", "app.actionsBar.emojiMenu.neutralLabel": "Ubesluttet", - "app.actionsBar.emojiMenu.neutralDesc": "Skift din status til: \"Ubesluttet\"", + "app.actionsBar.emojiMenu.neutralDesc": "Skift din status til: 'Ubesluttet'", "app.actionsBar.emojiMenu.confusedLabel": "Forvirret", - "app.actionsBar.emojiMenu.confusedDesc": "Skift din status til: \"Forvirret\"", + "app.actionsBar.emojiMenu.confusedDesc": "Skift din status til: 'Forvirret'", "app.actionsBar.emojiMenu.sadLabel": "Trist", - "app.actionsBar.emojiMenu.sadDesc": "Skift din status til: \"Trist\"", + "app.actionsBar.emojiMenu.sadDesc": "Skift din status til: 'Trist'", "app.actionsBar.emojiMenu.happyLabel": "Glad", - "app.actionsBar.emojiMenu.happyDesc": "Skift din status til: \"Glad\"", + "app.actionsBar.emojiMenu.happyDesc": "Skift din status til: 'Glad'", "app.actionsBar.emojiMenu.noneLabel": "Nulstil status", "app.actionsBar.emojiMenu.noneDesc": "Nulstil din status", "app.actionsBar.emojiMenu.applauseLabel": "Bifald", - "app.actionsBar.emojiMenu.applauseDesc": "Skift din status til: \"Bifald\"", + "app.actionsBar.emojiMenu.applauseDesc": "Skift din status til: 'Bifald'", "app.actionsBar.emojiMenu.thumbsUpLabel": "Thumbs up", - "app.actionsBar.emojiMenu.thumbsUpDesc": "Skift din status til: \"Thumps up\"", + "app.actionsBar.emojiMenu.thumbsUpDesc": "Skift din status til: 'Thumbs up'", "app.actionsBar.emojiMenu.thumbsDownLabel": "Thumps down", - "app.actionsBar.emojiMenu.thumbsDownDesc": "Skift din status til: \"Thumps down\"", + "app.actionsBar.emojiMenu.thumbsDownDesc": "Skift din status til: 'Thumbs down'", "app.actionsBar.currentStatusDesc": "Nuværende status {0}", "app.actionsBar.captions.start": "Start undertekster", "app.actionsBar.captions.stop": "Stop undertekster", "app.audioNotification.audioFailedMessage": "Din lyd, kunne ikke oprette forbindelse ", "app.audioNotification.mediaFailedMessage": "getUserMicMedia fejlede, da kun sikre oprindelser er tilladt", "app.audioNotification.closeLabel": "Luk", - "app.audioNotificaion.reconnectingAsListenOnly": "Mikrofonen er blevet lÃ¥st for deltagere, du er forbundet som \"Lytter kun\"", + "app.audioNotificaion.reconnectingAsListenOnly": "Mikrofonen er blevet lÃ¥st for deltagere, du er forbundet som 'Lytter kun'", "app.breakoutJoinConfirmation.title": "Tilslut breakout rum", "app.breakoutJoinConfirmation.message": "Vil du gerne tilslutte", "app.breakoutJoinConfirmation.confirmDesc": "Tilslut dig breakout rummet", @@ -547,22 +540,8 @@ "app.video.videoMenuDesc": "Ã…ben video dropdown menu", "app.video.chromeExtensionError": "Installer venligst", "app.video.chromeExtensionErrorLink": "denne Google Chrome tilføjelse", - "app.video.stats.title": "Forbindelsesstatus", - "app.video.stats.packetsReceived": "Pakker modtaget", - "app.video.stats.packetsSent": "Pakker sendt", - "app.video.stats.packetsLost": "Pakker tabt", - "app.video.stats.bitrate": "Bitrate", - "app.video.stats.lostPercentage": "Total procentdel tabt", - "app.video.stats.lostRecentPercentage": "Seneste procentdel tabt", - "app.video.stats.dimensions": "Dimensioner ", - "app.video.stats.codec": "Codec", - "app.video.stats.decodeDelay": "Afkodnings forsinkelse", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Encode brug", - "app.video.stats.currentDelay": "Nuværende forsinkelse ", "app.fullscreenButton.label": "Gør {0} fuld skærm", "app.meeting.endNotification.ok.label": "OK", - "app.whiteboard.annotations.poll": "Afstemningsresultaterne er udgivet", "app.whiteboard.toolbar.tools": "Værktøjer", "app.whiteboard.toolbar.tools.hand": "Pan", "app.whiteboard.toolbar.tools.pencil": "Blyant", @@ -643,7 +622,6 @@ "app.externalVideo.autoPlayWarning": "Afspil videoen for at aktivere medie synkronisering", "app.network.connection.effective.slow": "Vi oplever forbindelsesproblemer", "app.network.connection.effective.slow.help": "Mere information", - "app.externalVideo.noteLabel": "Note: Delte eksterne videoer vises ikke i optagelsen. YouTube, Vimeo, Instructure Media, Twitch og Daily Motion URL'er understøttes.", "app.actionsBar.actionsDropdown.shareExternalVideo": "Del en ekstern video", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Stop deling af ekstern video", "app.iOSWarning.label": "Opgrader venligst til iOS 12.2 eller højere ", diff --git a/bigbluebutton-html5/private/locales/de.json b/bigbluebutton-html5/private/locales/de.json index b39088e172b7ff49ae4a40cfa2d2d21a0e73d0ff..e611e80eb21450fdcd25b534273b25cc154fbbff 100644 --- a/bigbluebutton-html5/private/locales/de.json +++ b/bigbluebutton-html5/private/locales/de.json @@ -18,7 +18,8 @@ "app.chat.dropdown.save": "Speichern", "app.chat.label": "Chat", "app.chat.offline": "Offline", - "app.chat.emptyLogLabel": "Chat-Log ist leer", + "app.chat.pollResult": "Umfrageergebnisse", + "app.chat.emptyLogLabel": "Chatprotokoll ist leer", "app.chat.clearPublicChatMessage": "Der öffentliche Chatverlauf wurde durch einen Moderator gelöscht", "app.chat.multi.typing": "Mehrere Teilnehmer tippen", "app.chat.one.typing": "{0} tippt", @@ -50,10 +51,10 @@ "app.note.title": "Geteilte Notizen", "app.note.label": "Notiz", "app.note.hideNoteLabel": "Notiz verbergen", + "app.note.tipLabel": "Drücken Sie Esc, um die Editorwerkzeugliste auszuwählen", "app.user.activityCheck": "Teilnehmeraktivitätsprüfung", "app.user.activityCheck.label": "Prüfen, ob der Teilnehmer noch in der Konferenz ist ({0})", "app.user.activityCheck.check": "Prüfen", - "app.note.tipLabel": "Drücken Sie Esc, um die Editorwerkzeugliste auszuwählen", "app.userList.usersTitle": "Teilnehmer", "app.userList.participantsTitle": "Teilnehmer", "app.userList.messagesTitle": "Nachrichten", @@ -95,6 +96,8 @@ "app.userList.userOptions.unmuteAllDesc": "Hebt die Konferenz-Stummschaltung auf", "app.userList.userOptions.lockViewersLabel": "Teilnehmerrechte einschränken", "app.userList.userOptions.lockViewersDesc": "Schränkt bestimmte Funktionen der Konferenzteilnehmer ein", + "app.userList.userOptions.connectionStatusLabel": "Verbindungsstatus", + "app.userList.userOptions.connectionStatusDesc": "Verbindungsstatus der Teilnehmer anzeigen", "app.userList.userOptions.disableCam": "Teilnehmerwebcams sind deaktiviert", "app.userList.userOptions.disableMic": "Teilnehmermikrofone sind deaktiviert", "app.userList.userOptions.disablePrivChat": "Privater Chat ist deaktiviert", @@ -126,10 +129,13 @@ "app.meeting.meetingTimeRemaining": "Verbleibende Konferenzzeit: {0}", "app.meeting.meetingTimeHasEnded": "Die Zeit ist abgelaufen. Die Konferenz wird in Kürze beendet", "app.meeting.endedMessage": "Sie werden zum Startbildschirm weitergeleitet", - "app.meeting.alertMeetingEndsUnderOneMinute": "Die Konferenz wird in einer Minute beendet.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Breakout-Sitzung wird in einer Minute beendet.", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "Die Konferenz endet in einer Minute.", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "Die Konferenz endet in {0} Minuten.", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "Gruppensitzung endet in {0} Minuten.", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "Gruppensitzung endet in einer Minute.", "app.presentation.hide": "Präsentation verbergen", "app.presentation.notificationLabel": "Aktuelle Präsentation", + "app.presentation.downloadLabel": "Download", "app.presentation.slideContent": "Folieninhalt", "app.presentation.startSlideContent": "Beginn des Folieninhalts", "app.presentation.endSlideContent": "Ende des Folieninhalts", @@ -174,6 +180,7 @@ "app.presentationUploder.rejectedError": "Die ausgewählten Dateien wurden zurückgewiesen. Bitte prüfen Sie die zulässigen Dateitypen.", "app.presentationUploder.upload.progress": "Hochladen ({0}%)", "app.presentationUploder.upload.413": "Die Datei ist zu groß. Bitte teilen Sie sie in mehrere kleinere Dateien auf.", + "app.presentationUploder.genericError": "Ups, irgendwas ist schiefgelaufen ...", "app.presentationUploder.upload.408": "Zeitüberschreitung des Upload-Token anfordern.", "app.presentationUploder.upload.404": "404: Ungültiger Upload-Token", "app.presentationUploder.upload.401": "Anforderung des Upload-Tokens von Präsentationen fehlgeschlagen.", @@ -195,6 +202,13 @@ "app.presentationUploder.tableHeading.filename": "Dateiname", "app.presentationUploder.tableHeading.options": "Optionen", "app.presentationUploder.tableHeading.status": "Status", + "app.presentationUploder.uploading": "Hochladen {0} {1}", + "app.presentationUploder.uploadStatus": "{0} von {1} Uploads abgeschlossen", + "app.presentationUploder.completed": "{0} Uploads abgeschlossen", + "app.presentationUploder.item" : "Element", + "app.presentationUploder.itemPlural" : "Elemente", + "app.presentationUploder.clearErrors": "Fehler löschen", + "app.presentationUploder.clearErrorsDesc": "Löscht fehlgeschlagene Präsentationsuploads", "app.poll.pollPaneTitle": "Umfrage", "app.poll.quickPollTitle": "Schnellumfrage", "app.poll.hidePollDesc": "Versteckt das Umfragemenü", @@ -202,7 +216,7 @@ "app.poll.quickPollInstruction": "Wählen Sie eine der unten stehenden Optionen, um die Umfrage zu starten.", "app.poll.customPollLabel": "Benutzerdefinierte Umfrage", "app.poll.startCustomLabel": "Benutzerdefinierte Umfrage starten", - "app.poll.activePollInstruction": "Lassen Sie dieses Fenster offen, um auf die Antworten der Teilnehmer zu warten. Sobald Sie auf \"Umfrageergebnisse veröffentlichen\" klicken, werden die Ergebnisse angezeigt und die Umfrage beendet.", + "app.poll.activePollInstruction": "Lassen Sie dieses Fenster offen, um auf die Antworten der Teilnehmer zu warten. Sobald Sie auf 'Umfrageergebnisse veröffentlichen' klicken, werden die Ergebnisse angezeigt und die Umfrage beendet.", "app.poll.publishLabel": "Umfrageergebnisse veröffentlichen", "app.poll.backLabel": "Zurück zu den Umfrageoptionen", "app.poll.closeLabel": "Schließen", @@ -240,6 +254,7 @@ "app.connectingMessage": "Verbinde...", "app.waitingMessage": "Verbindung unterbrochen. Versuche in {0} Sekunden erneut zu verbinden...", "app.retryNow": "Jetzt erneut versuchen", + "app.muteWarning.label": "Klicken Sie auf {0}, um Ihre Stummschaltung aufzuheben.", "app.navBar.settingsDropdown.optionsLabel": "Optionen", "app.navBar.settingsDropdown.fullscreenLabel": "Als Vollbild darstellen", "app.navBar.settingsDropdown.settingsLabel": "Einstellungen öffnen", @@ -267,7 +282,7 @@ "app.leaveConfirmation.confirmLabel": "Verlassen", "app.leaveConfirmation.confirmDesc": "Hiermit verlassen Sie Konferenz", "app.endMeeting.title": "Konferenz beenden", - "app.endMeeting.description": "Sind Sie sicher, dass Sie die Konferenz beenden wollen?", + "app.endMeeting.description": "Sind Sie sicher, dass Sie diese Konferenz für alle beenden wollen (die Verbindung aller Teilnehmer wird getrennt)?", "app.endMeeting.yesLabel": "Ja", "app.endMeeting.noLabel": "Nein", "app.about.title": "Versionsinfo", @@ -288,10 +303,6 @@ "app.screenshare.screenShareLabel" : "Bildschirmfreigabe", "app.submenu.application.applicationSectionTitle": "Anwendung", "app.submenu.application.animationsLabel": "Animationen", - "app.submenu.application.audioAlertLabel": "Audiowarnungen für Chat", - "app.submenu.application.pushAlertLabel": "Popupwarnungen für Chat", - "app.submenu.application.userJoinAudioAlertLabel": "Audiowarnton wenn neue Teilnehmer den Raum betreten", - "app.submenu.application.userJoinPushAlertLabel": "Popupnachricht wenn neue Teilnehmer den Raum betreten", "app.submenu.application.fontSizeControlLabel": "Schriftgröße", "app.submenu.application.increaseFontBtnLabel": "Schriftgröße erhöhen", "app.submenu.application.decreaseFontBtnLabel": "Schriftgröße verringern", @@ -299,6 +310,12 @@ "app.submenu.application.languageLabel": "Sprache", "app.submenu.application.languageOptionLabel": "Sprache auswählen", "app.submenu.application.noLocaleOptionLabel": "Keine Sprachschemata verfügbar", + "app.submenu.notification.SectionTitle": "Benachrichtigungen", + "app.submenu.notification.Desc": "Definieren Sie, wie und was Ihnen mitgeteilt wird.", + "app.submenu.notification.audioAlertLabel": "Audio-Hinweise", + "app.submenu.notification.pushAlertLabel": "Popup-Hinweise", + "app.submenu.notification.messagesLabel": "Chatnachricht", + "app.submenu.notification.userJoinLabel": "Teilnehmer beitreten", "app.submenu.audio.micSourceLabel": "Mikrofonauswahl", "app.submenu.audio.speakerSourceLabel": "Lautsprecherauswahl", "app.submenu.audio.streamVolumeLabel": "Ihre Audiolautstärke", @@ -322,9 +339,13 @@ "app.settings.dataSavingTab.screenShare": "Bildschirmfreigabe aktiviert", "app.settings.dataSavingTab.description": "Um Datentransfervolumen zu sparen, können Sie hier einstellen, was angezeigt wird.", "app.settings.save-notification.label": "Einstellungen wurden gespeichert", + "app.statusNotifier.lowerHands": "Senkende Hände", + "app.statusNotifier.raisedHandsTitle": "Gehobene Hände", + "app.statusNotifier.raisedHandDesc": "{0} haben ihre Hand gehoben", + "app.statusNotifier.and": "und", "app.switch.onLabel": "AN", "app.switch.offLabel": "AUS", - "app.talkingIndicator.ariaMuteDesc" : "Auswählen um Teilnehmer stummzuschalten", + "app.talkingIndicator.ariaMuteDesc" : "Auswählen, um Teilnehmer stummzuschalten", "app.talkingIndicator.isTalking" : "{0} spricht", "app.talkingIndicator.wasTalking" : "{0} spricht nicht mehr", "app.actionsBar.actionsDropdown.actionsLabel": "Aktionen", @@ -340,8 +361,8 @@ "app.actionsBar.actionsDropdown.pollBtnLabel": "Umfrage starten", "app.actionsBar.actionsDropdown.pollBtnDesc": "Umschalten des Umfragemenüs", "app.actionsBar.actionsDropdown.saveUserNames": "Teilnehmernamen speichern", - "app.actionsBar.actionsDropdown.createBreakoutRoom": "Breakout-Räume erstellen", - "app.actionsBar.actionsDropdown.createBreakoutRoomDesc": "ermöglicht die aktuelle Konferenz in mehrere Räume aufzuteilen", + "app.actionsBar.actionsDropdown.createBreakoutRoom": "Gruppenräume erstellen", + "app.actionsBar.actionsDropdown.createBreakoutRoomDesc": "ermöglicht die aktuelle Konferenz in mehrere Gruppenräume aufzuteilen", "app.actionsBar.actionsDropdown.captionsLabel": "Untertitel schreiben", "app.actionsBar.actionsDropdown.captionsDesc": "Untertitelfenster umschalten", "app.actionsBar.actionsDropdown.takePresenter": "Zum Präsentator werden", @@ -386,14 +407,14 @@ "app.audioNotification.mediaFailedMessage": "getUserMicMedia fehlgeschlagen, weil nur sichere Quellen erlaubt sind", "app.audioNotification.closeLabel": "Schließen", "app.audioNotificaion.reconnectingAsListenOnly": "Mikrofone sind für Teilnehmer gesperrt, Sie werden nur zum Zuhören verbunden", - "app.breakoutJoinConfirmation.title": "Breakout-Raum beitreten", + "app.breakoutJoinConfirmation.title": "Gruppenraum beitreten", "app.breakoutJoinConfirmation.message": "Möchten Sie beitreten", - "app.breakoutJoinConfirmation.confirmDesc": "Dem Breakout-Raum beitreten", + "app.breakoutJoinConfirmation.confirmDesc": "Dem Gruppenraum beitreten", "app.breakoutJoinConfirmation.dismissLabel": "Abbrechen", - "app.breakoutJoinConfirmation.dismissDesc": "Schließen und die Teilnahme am Breakout-Raum verweigern", - "app.breakoutJoinConfirmation.freeJoinMessage": "Wählen Sie den Breakout-Raum aus, dem sie beitreten wollen", - "app.breakoutTimeRemainingMessage": "Verbleibende Breakout-Raum Zeit: {0}", - "app.breakoutWillCloseMessage": "Zeit abgelaufen. Der Breakout-Raum wird in Kürze geschlossen.", + "app.breakoutJoinConfirmation.dismissDesc": "Schließen und die Teilnahme im Gruppenraum verweigern", + "app.breakoutJoinConfirmation.freeJoinMessage": "Wählen Sie den Gruppenraum aus, dem sie beitreten wollen", + "app.breakoutTimeRemainingMessage": "Verbleibende Gruppenraumzeit: {0}", + "app.breakoutWillCloseMessage": "Zeit abgelaufen. Der Gruppenraum wird in Kürze geschlossen.", "app.calculatingBreakoutTimeRemaining": "Berechne verbleibende Zeit...", "app.audioModal.ariaTitle": "Audioteilnahmedialog", "app.audioModal.microphoneLabel": "Mit Mikrofon", @@ -486,20 +507,23 @@ "app.userList.guest.pendingGuestUsers": "{0} unbearbeitete Gäste", "app.userList.guest.pendingGuestAlert": "Ist der Konferenz beigetreten und wartet auf Ihre Teilnahmeerlaubnis", "app.userList.guest.rememberChoice": "Auswahl für die Zukunft speichern", + "app.userList.guest.acceptLabel": "Akzeptieren", + "app.userList.guest.denyLabel": "Ablehnen", "app.user-info.title": "Verzeichnissuche", - "app.toast.breakoutRoomEnded": "Breakout-Raum wurde beendet. Bitte treten Sie der Audiokonferenz erneut bei.", + "app.toast.breakoutRoomEnded": "Gruppenraum wurde geschlossen. Bitte treten Sie der Audiokonferenz erneut bei.", "app.toast.chat.public": "Neue öffentliche Chatnachricht", "app.toast.chat.private": "Neue private Chatnachricht", "app.toast.chat.system": "System", "app.toast.clearedEmoji.label": "Emojistatus zurückgesetzt", "app.toast.setEmoji.label": "Emojistatus auf {0} gesetzt", "app.toast.meetingMuteOn.label": "Alle Teilnehmer wurden stummgeschaltet", - "app.toast.meetingMuteOff.label": "Konferenzstummschaltung ausgeschaltet", + "app.toast.meetingMuteOff.label": "Konferenz-Stummschaltung ausgeschaltet", "app.notification.recordingStart": "Diese Konferenz wird jetzt aufgezeichnet", "app.notification.recordingStop": "Diese Konferenz wird nicht aufgezeichnet", "app.notification.recordingPaused": "Diese Konferenz wird nicht mehr aufgezeichnet", "app.notification.recordingAriaLabel": "Aufgezeichnete Zeit", "app.notification.userJoinPushAlert": "{0} ist der Konferenz beigetreten", + "app.submenu.notification.raiseHandLabel": "Hand heben", "app.shortcut-help.title": "Tastaturkürzel", "app.shortcut-help.accessKeyNotAvailable": "Zugriffsschlüssel sind nicht verfügbar", "app.shortcut-help.comboLabel": "Tastenkombination", @@ -533,6 +557,12 @@ "app.lock-viewers.button.cancel": "Abbrechen", "app.lock-viewers.locked": "Gesperrt", "app.lock-viewers.unlocked": "Freigegeben", + "app.connection-status.ariaTitle": "Verbindungsstatus", + "app.connection-status.title": "Verbindungsstatus", + "app.connection-status.description": "Verbindungsstatus der Teilnehmer anzeigen", + "app.connection-status.empty": "Es wurde bisher kein Verbindungsproblem gemeldet", + "app.connection-status.more": "mehr", + "app.connection-status.offline": "Offline", "app.recording.startTitle": "Aufzeichnung starten", "app.recording.stopTitle": "Aufzeichnung pausieren", "app.recording.resumeTitle": "Aufzeichnung fortsetzen", @@ -540,10 +570,17 @@ "app.recording.stopDescription": "Sind Sie sicher, dass Sie die Aufnahme pausieren wollen? Sie können Sie durch erneutes Drücken des Aufnahmeknopfs jederzeit fortsetzen.", "app.videoPreview.cameraLabel": "Kamera", "app.videoPreview.profileLabel": "Qualität", + "app.videoPreview.quality.low": "Niedrig", + "app.videoPreview.quality.medium": "Mittel", + "app.videoPreview.quality.high": "Hoch", + "app.videoPreview.quality.hd": "High Definition", "app.videoPreview.cancelLabel": "Abbrechen", "app.videoPreview.closeLabel": "Schließen", "app.videoPreview.findingWebcamsLabel": "Suche Webcams", "app.videoPreview.startSharingLabel": "Freigabe starten", + "app.videoPreview.stopSharingLabel": "Freigabe beenden", + "app.videoPreview.stopSharingAllLabel": "Alles beenden", + "app.videoPreview.sharedCameraLabel": "Diese Kamera wird bereits freigegeben", "app.videoPreview.webcamOptionLabel": "Webcam auswählen", "app.videoPreview.webcamPreviewLabel": "Webcamvorschau", "app.videoPreview.webcamSettingsTitle": "Webcameinstellungen", @@ -573,19 +610,8 @@ "app.video.videoMenuDesc": "Videomenü öffnen", "app.video.chromeExtensionError": "Sie müssen Folgendes installieren:", "app.video.chromeExtensionErrorLink": "diese Chrome Erweiterung", - "app.video.stats.title": "Verbindungsstatistiken", - "app.video.stats.packetsReceived": "Empfangene Pakete", - "app.video.stats.packetsSent": "Gesendete Pakete", - "app.video.stats.packetsLost": "Verlorene Pakete", - "app.video.stats.bitrate": "Bitrate", - "app.video.stats.lostPercentage": "Verlustprozente insgesamt", - "app.video.stats.lostRecentPercentage": "Verlustprozente kürzlich", - "app.video.stats.dimensions": "Dimensionen", - "app.video.stats.codec": "Codec", - "app.video.stats.decodeDelay": "Dekodierungsverzögerung", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Enkodierungsnutzung", - "app.video.stats.currentDelay": "Aktuelle Verzögerung", + "app.video.pagination.prevPage": "Vorherige Videos ansehen", + "app.video.pagination.nextPage": "Nächste Videos ansehen", "app.fullscreenButton.label": "{0} zum Vollbild machen", "app.deskshare.iceConnectionStateError": "Verbindungsfehler beim Teilen des Bildschirms (Fehler 1108)", "app.sfu.mediaServerConnectionError2000": "Keine Verbindung zum Medienserver (Fehler 2000)", @@ -599,6 +625,7 @@ "app.sfu.noAvailableCodec2203": "Server konnte keinen passenden Code finden (Fehler 2203)", "app.meeting.endNotification.ok.label": "OK", "app.whiteboard.annotations.poll": "Umfrageergebnisse wurden veröffentlicht", + "app.whiteboard.annotations.pollResult": "Umfrageergebnis", "app.whiteboard.toolbar.tools": "Werkzeuge", "app.whiteboard.toolbar.tools.hand": "Verschieben", "app.whiteboard.toolbar.tools.pencil": "Stift", @@ -639,11 +666,11 @@ "app.videoDock.webcamUnfocusDesc": "Ausgewählte Webcam auf Normalgröße verkleinern", "app.videoDock.autoplayBlockedDesc": "Wir benötigen Ihre Zustimmung, um Ihnen die Webcams anderer Teilnehmer zu zeigen.", "app.videoDock.autoplayAllowLabel": "Webcams zeigen", - "app.invitation.title": "Breakoutraum-Einladung", + "app.invitation.title": "Gruppenraumeinladung", "app.invitation.confirm": "Einladen", - "app.createBreakoutRoom.title": "Breakout-Räume", - "app.createBreakoutRoom.ariaTitle": "Breakout-Räume verbergen", - "app.createBreakoutRoom.breakoutRoomLabel": "Breakout-Räume {0}", + "app.createBreakoutRoom.title": "Gruppenräume", + "app.createBreakoutRoom.ariaTitle": "Gruppenräume verbergen", + "app.createBreakoutRoom.breakoutRoomLabel": "Gruppenräume {0}", "app.createBreakoutRoom.generatingURL": "Erzeuge URL", "app.createBreakoutRoom.generatedURL": "Erzeugt", "app.createBreakoutRoom.duration": "Dauer {0}", @@ -658,16 +685,16 @@ "app.createBreakoutRoom.numberOfRooms": "Anzahl der Räume", "app.createBreakoutRoom.durationInMinutes": "Dauer (Minuten)", "app.createBreakoutRoom.randomlyAssign": "Zufällig zuordnen", - "app.createBreakoutRoom.endAllBreakouts": "Alle Breakout-Räume beenden", + "app.createBreakoutRoom.endAllBreakouts": "Alle Gruppenräume beenden", "app.createBreakoutRoom.roomName": "{0} (Raum - {1})", "app.createBreakoutRoom.doneLabel": "Fertig", "app.createBreakoutRoom.nextLabel": "Nächster", - "app.createBreakoutRoom.minusRoomTime": "Breakoutraumzeit verringern auf", - "app.createBreakoutRoom.addRoomTime": "Breakoutraumzeit erhöhen auf", + "app.createBreakoutRoom.minusRoomTime": "Gruppenraumzeit verringern auf", + "app.createBreakoutRoom.addRoomTime": "Gruppenraumzeit erhöhen auf", "app.createBreakoutRoom.addParticipantLabel": "+ Teilnehmer hinzufügen", - "app.createBreakoutRoom.freeJoin": "Den Teilnehmern erlauben, sich selber einen Breakout-Raum auszusuchen.", - "app.createBreakoutRoom.leastOneWarnBreakout": "Jedem Breakout-Raum muss wenigstens ein Teilnehmer zugeordnet sein.", - "app.createBreakoutRoom.modalDesc": "Tipp: Sie können per drag-and-drop die Teilnehmer einem bestimmten Breakout-Raum zuweisen.", + "app.createBreakoutRoom.freeJoin": "Den Teilnehmern erlauben, sich selber einen Gruppenraum auszusuchen.", + "app.createBreakoutRoom.leastOneWarnBreakout": "Jedem Gruppenraum muss wenigstens ein Teilnehmer zugeordnet sein.", + "app.createBreakoutRoom.modalDesc": "Tipp: Sie können per Drag-and-Drop die Teilnehmer einem bestimmten Gruppenraum zuweisen.", "app.createBreakoutRoom.roomTime": "{0} Minuten", "app.createBreakoutRoom.numberOfRoomsError": "Die Raumanzahl ist ungültig.", "app.externalVideo.start": "Neues Video teilen", @@ -679,7 +706,7 @@ "app.externalVideo.autoPlayWarning": "Video abspielen, um Mediensynchronisation zu aktivieren", "app.network.connection.effective.slow": "Es wurden Verbindungsprobleme beobachtet.", "app.network.connection.effective.slow.help": "Weitere Informationen", - "app.externalVideo.noteLabel": "Hinweis: Geteilte externe Videos werden nicht in der Aufzeichnung enthalten sein. Youtube, Vimeo, Instructure Media, Twitch und Daily Motion URLs werden unterstützt.", + "app.externalVideo.noteLabel": "Hinweis: Freigegebene externe Videos werden in der Aufzeichnung nicht angezeigt. YouTube, Vimeo, Instructure Media, Twitch, Dailymotion und Mediendatei-URLs (z.B. https://beispiel.de/xy.mp4) werden unterstützt.", "app.actionsBar.actionsDropdown.shareExternalVideo": "Externes Video teilen", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Teilen von externem Video beenden", "app.iOSWarning.label": "Bitte aktualisieren Sie auf iOS 12.2 oder höher", diff --git a/bigbluebutton-html5/private/locales/el_GR.json b/bigbluebutton-html5/private/locales/el_GR.json index d327abb999b9ec21e42291c7358edd189a05a4c3..5eb8d83ca1c782f9a9dff895b2bcde58425fe498 100644 --- a/bigbluebutton-html5/private/locales/el_GR.json +++ b/bigbluebutton-html5/private/locales/el_GR.json @@ -1,6 +1,6 @@ { - "app.home.greeting": "Η παÏουσίαση θα ξεκινήσει σε λίγο...", - "app.chat.submitLabel": "Αποστολή ΜηνÏματος", + "app.home.greeting": "Η παÏουσίασή σας θα ξεκινήσει σε λίγο...", + "app.chat.submitLabel": "Αποστολή μηνÏματος", "app.chat.errorMaxMessageLength": "Το μήνυμα είναι {0} χαÏακτήÏες(ας). Î Î¿Î»Ï Î¼ÎµÎ³Î¬Î»Î¿", "app.chat.disconnected": "Έχετε αποσυνδεθεί, δεν μποÏοÏν να αποστελλόνται μηνÏματα", "app.chat.locked": "Η συνομιλία είναι κλειδωμÎνη, δεν μποÏοÏν να αποστελλόνται μηνÏματα", @@ -18,6 +18,7 @@ "app.chat.dropdown.save": "Αποθήκευση", "app.chat.label": "Συνομιλία", "app.chat.offline": "Εκτός σÏνδεσης", + "app.chat.pollResult": "ΑποτελÎσματα ψηφοφοÏίας", "app.chat.emptyLogLabel": "ΑÏχείο συνομιλίας άδειο", "app.chat.clearPublicChatMessage": "Το ιστοÏικό δημόσιας συνομιλίας Îχει καθαÏιστεί από το συντονιστή", "app.chat.multi.typing": "Πολλαπλοί χÏήστες πληκτÏολογοÏν", @@ -45,10 +46,10 @@ "app.note.title": "ΚοινόχÏηστες Σημειώσεις", "app.note.label": "Σημείωση", "app.note.hideNoteLabel": "ΑπόκÏυψη σημείωσης", + "app.note.tipLabel": "Πατήστε Esc για εστίαση στη γÏαμμή εÏγαλείων επεξεÏγαστή", "app.user.activityCheck": "Έλεγχος δÏαστηÏιότητας χÏηστών", "app.user.activityCheck.label": "Έλεγχος αν υπάÏχουν ακόμα χÏήστες μÎσα στη συνάντηση ({0})", "app.user.activityCheck.check": "Έλεγχος", - "app.note.tipLabel": "Πατήστε Esc για εστίαση στη γÏαμμή εÏγαλείων επεξεÏγαστή", "app.userList.usersTitle": "ΧÏήστες", "app.userList.participantsTitle": "ΣυμμετÎχοντες", "app.userList.messagesTitle": "ΜηνÏματα", @@ -67,6 +68,8 @@ "app.userList.menu.chat.label": "ΈναÏξη ιδιωτικής συνομιλίας", "app.userList.menu.clearStatus.label": "ΚαθαÏισμός κατάστασης", "app.userList.menu.removeUser.label": "ΑπομάκÏυνση χÏήστη", + "app.userList.menu.removeConfirmation.label": "ΑφαίÏεση χÏήστη ({0})", + "app.userlist.menu.removeConfirmation.desc": "ΑποτÏÎψτε την επανασÏνδεση Î±Ï…Ï„Î¿Ï Ï„Î¿Ï… χÏήστη στη συνεδÏία.", "app.userList.menu.muteUserAudio.label": "Σίγαση χÏήστη", "app.userList.menu.unmuteUserAudio.label": "Απο-σίγαση χÏήστη", "app.userList.userAriaLabel": "{0} {1} {2} Κατάσταση {3}", @@ -87,6 +90,8 @@ "app.userList.userOptions.unmuteAllDesc": "ΑπενεÏγοποίηση σίγασης σÏσκεψης", "app.userList.userOptions.lockViewersLabel": "Κλείδωμα θεατών", "app.userList.userOptions.lockViewersDesc": "Κλείδωμα συγκεκÏιμÎνων λειτουÏγιών των συμμετεχόντων της σÏσκεψης", + "app.userList.userOptions.connectionStatusLabel": "Κατάσταση σÏνδεσης", + "app.userList.userOptions.connectionStatusDesc": "Î Ïοβολή κατάστασης σÏνδεσης του χÏήστη", "app.userList.userOptions.disableCam": "Οι κάμεÏες των θεατών είναι απενεÏγοποιημÎνες", "app.userList.userOptions.disableMic": "Τα μικÏόφωνο των θεατών είναι ανενεÏγά", "app.userList.userOptions.disablePrivChat": "Ιδιωτική συνομιλία είναι απενεÏγοποιημÎνη", @@ -106,14 +111,14 @@ "app.media.autoplayAlertDesc": "ΕπιτÏÎπεται Ï€Ïόσβαση", "app.media.screenshare.start": "Ο διαμοιÏασμός οθόνης Îχει αÏχίσει", "app.media.screenshare.end": "Ο διαμοιÏασμός οθόνης Îχει τελειώσει", + "app.media.screenshare.unavailable": "Ο διαμοιÏασμός οθόνης δεν είναι διαθÎσιμος", + "app.media.screenshare.notSupported": "Ο διαμοιÏασμός οθόνης δεν είναι διαθÎσιμος σε αυτόν τον πεÏιηγητή διαδικτÏου.", "app.media.screenshare.autoplayBlockedDesc": "ΧÏειαζόμαστε την άδειά σας για να σας δείξουμε την οθόνη του παÏουσιαστή.", "app.media.screenshare.autoplayAllowLabel": "Î Ïοβολή διαμοιÏαζόμενης οθόνης", "app.meeting.ended": "Η σÏνοδος Îχει λήξει", "app.meeting.meetingTimeRemaining": "ΧÏόνος συνεδÏίας που απομÎνει: {0}", "app.meeting.meetingTimeHasEnded": "Ο χÏόνος τελείωσε. Η συνεδÏία θα κλείσει σÏντομα", "app.meeting.endedMessage": "Θα μεταφεÏθείτε πίσω στην αÏχική σας σελίδα", - "app.meeting.alertMeetingEndsUnderOneMinute": "Η συνεδÏία θα κλείσει σε Îνα λεπτό.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Ο διαχωÏισμός θα κλείσει σε Îνα λεπτό", "app.presentation.hide": "ΑπόκÏυψη παÏουσίασης", "app.presentation.notificationLabel": "ΤÏÎχουσα παÏουσίαση", "app.presentation.slideContent": "ΠεÏιεχόμενα διαφάνειας", @@ -220,7 +225,6 @@ "app.leaveConfirmation.confirmLabel": "ΑποχώÏηση", "app.leaveConfirmation.confirmDesc": "Σας αποσυνδÎει από αυτή τη συνεδÏία", "app.endMeeting.title": "ΤεÏματισμός συνεδÏίας", - "app.endMeeting.description": "Είστε σίγουÏοι ότι θÎλετε να τεÏματίσετε αυτή τη σÏνοδο;", "app.endMeeting.yesLabel": "Îαι", "app.endMeeting.noLabel": "Όχι", "app.about.title": "ΠεÏί", @@ -415,9 +419,6 @@ "app.video.videoMenuDesc": "Άνοιγμα αναπτυσσόμενης λίστας βίντεο", "app.video.chromeExtensionError": "Î ÏÎπει να εγκαταστήσετε", "app.video.chromeExtensionErrorLink": "αυτή την επÎκταση Chrome", - "app.video.stats.title": "Στατιστικά σÏνδεσης", - "app.video.stats.dimensions": "Διαστάσεις", - "app.video.stats.currentDelay": "ΤÏÎχουσα καθυστÎÏηση", "app.meeting.endNotification.ok.label": "OK", "app.whiteboard.toolbar.tools": "ΕÏγαλεία", "app.whiteboard.toolbar.tools.hand": "Pan", diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json index cc6ff15f39ec68c0c3ddd42ca91f5c63d6e96889..ce97c08a811a213688c88633b6f9469a53065088 100755 --- a/bigbluebutton-html5/private/locales/en.json +++ b/bigbluebutton-html5/private/locales/en.json @@ -51,10 +51,10 @@ "app.note.title": "Shared Notes", "app.note.label": "Note", "app.note.hideNoteLabel": "Hide note", + "app.note.tipLabel": "Press Esc to focus editor toolbar", "app.user.activityCheck": "User activity check", "app.user.activityCheck.label": "Check if user is still in meeting ({0})", "app.user.activityCheck.check": "Check", - "app.note.tipLabel": "Press Esc to focus editor toolbar", "app.userList.usersTitle": "Users", "app.userList.participantsTitle": "Participants", "app.userList.messagesTitle": "Messages", @@ -113,6 +113,9 @@ "app.userList.userOptions.enableNote": "Shared notes are now enabled", "app.userList.userOptions.showUserList": "User list is now shown to viewers", "app.userList.userOptions.enableOnlyModeratorWebcam": "You can enable your webcam now, everyone will see you", + "app.userList.userOptions.savedNames.title": "List of users in meeting {0} at {1}", + "app.userList.userOptions.sortedFirstName.heading": "Sorted by first name:", + "app.userList.userOptions.sortedLastName.heading": "Sorted by last name:", "app.media.label": "Media", "app.media.autoplayAlertDesc": "Allow Access", "app.media.screenshare.start": "Screenshare has started", @@ -135,6 +138,7 @@ "app.meeting.alertBreakoutEndsUnderMinutesSingular": "Breakout is closing in one minute.", "app.presentation.hide": "Hide presentation", "app.presentation.notificationLabel": "Current presentation", + "app.presentation.downloadLabel": "Download", "app.presentation.slideContent": "Slide Content", "app.presentation.startSlideContent": "Slide content start", "app.presentation.endSlideContent": "Slide content end", @@ -569,6 +573,10 @@ "app.recording.stopDescription": "Are you sure you want to pause the recording? You can resume by selecting the record button again.", "app.videoPreview.cameraLabel": "Camera", "app.videoPreview.profileLabel": "Quality", + "app.videoPreview.quality.low": "Low", + "app.videoPreview.quality.medium": "Medium", + "app.videoPreview.quality.high": "High", + "app.videoPreview.quality.hd": "High definition", "app.videoPreview.cancelLabel": "Cancel", "app.videoPreview.closeLabel": "Close", "app.videoPreview.findingWebcamsLabel": "Finding webcams", @@ -582,6 +590,8 @@ "app.videoPreview.webcamNotFoundLabel": "Webcam not found", "app.videoPreview.profileNotFoundLabel": "No supported camera profile", "app.video.joinVideo": "Share webcam", + "app.video.connecting": "Webcam sharing is starting ...", + "app.video.dataSaving": "Webcam sharing is disabled in Data Saving", "app.video.leaveVideo": "Stop sharing webcam", "app.video.iceCandidateError": "Error on adding ICE candidate", "app.video.iceConnectionStateError": "Connection failure (ICE error 1107)", @@ -605,19 +615,9 @@ "app.video.videoMenuDesc": "Open video menu dropdown", "app.video.chromeExtensionError": "You must install", "app.video.chromeExtensionErrorLink": "this Chrome extension", - "app.video.stats.title": "Connection Stats", - "app.video.stats.packetsReceived": "Packets received", - "app.video.stats.packetsSent": "Packets sent", - "app.video.stats.packetsLost": "Packets lost", - "app.video.stats.bitrate": "Bitrate", - "app.video.stats.lostPercentage": "Total percentage lost", - "app.video.stats.lostRecentPercentage": "Recent percentage lost", - "app.video.stats.dimensions": "Dimensions", - "app.video.stats.codec": "Codec", - "app.video.stats.decodeDelay": "Decode delay", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Encode usage", - "app.video.stats.currentDelay": "Current delay", + "app.video.pagination.prevPage": "See previous videos", + "app.video.pagination.nextPage": "See next videos", + "app.video.clientDisconnected": "Webcam cannot be shared due to connection issues", "app.fullscreenButton.label": "Make {0} fullscreen", "app.deskshare.iceConnectionStateError": "Connection failed when sharing screen (ICE error 1108)", "app.sfu.mediaServerConnectionError2000": "Unable to connect to media server (error 2000)", @@ -630,7 +630,8 @@ "app.sfu.invalidSdp2202":"Client generated an invalid media request (SDP error 2202)", "app.sfu.noAvailableCodec2203": "Server could not find an appropriate codec (error 2203)", "app.meeting.endNotification.ok.label": "OK", - "app.whiteboard.annotations.poll": "Poll results were published to Public Chat and Whiteboard", + "app.whiteboard.annotations.poll": "Poll results were published", + "app.whiteboard.annotations.pollResult": "Poll Result", "app.whiteboard.toolbar.tools": "Tools", "app.whiteboard.toolbar.tools.hand": "Pan", "app.whiteboard.toolbar.tools.pencil": "Pencil", diff --git a/bigbluebutton-html5/private/locales/eo.json b/bigbluebutton-html5/private/locales/eo.json new file mode 100644 index 0000000000000000000000000000000000000000..d83de25d2b890593cc5871686deaea4432d7691a --- /dev/null +++ b/bigbluebutton-html5/private/locales/eo.json @@ -0,0 +1,707 @@ +{ + "app.home.greeting": "Via prezentaĵo komenciÄos baldaÅ ...", + "app.chat.submitLabel": "Sendi mesaÄon", + "app.chat.errorMaxMessageLength": "La mesaÄo estas tro longa je {0} signo(j)", + "app.chat.disconnected": "Vi malkonektiÄis, mesaÄoj ne sendeblas", + "app.chat.locked": "La babilejo estas Ålosita, mesaÄoj ne sendeblas", + "app.chat.inputLabel": "Tajpu mesaÄon por {0}", + "app.chat.inputPlaceholder": "Sendi mesaÄon al {0}", + "app.chat.titlePublic": "Publika babilejo", + "app.chat.titlePrivate": "Privata babilejo kun {0}", + "app.chat.partnerDisconnected": "{0} forlasis la kunsidon", + "app.chat.closeChatLabel": "Fermi {0}", + "app.chat.hideChatLabel": "KaÅi {0}", + "app.chat.moreMessages": "Pli da mesaÄoj sube", + "app.chat.dropdown.options": "Babilaj agordoj", + "app.chat.dropdown.clear": "ViÅi", + "app.chat.dropdown.copy": "Kopii", + "app.chat.dropdown.save": "Konservi", + "app.chat.label": "Babilejo", + "app.chat.offline": "Nekonektite", + "app.chat.pollResult": "Enketrezultoj", + "app.chat.emptyLogLabel": "Babilprotokolo malplenas", + "app.chat.clearPublicChatMessage": "La publikan babilhistorion viÅis administranto", + "app.chat.multi.typing": "Pluraj uzantoj tajpas", + "app.chat.one.typing": "{0} tajpas", + "app.chat.two.typing": "{0} kaj {1} tajpas", + "app.captions.label": "Subtekstoj", + "app.captions.menu.close": "Fermi", + "app.captions.menu.start": "Komenci", + "app.captions.menu.ariaStart": "Komencu skribi subtekston", + "app.captions.menu.ariaStartDesc": "Malfermas subtekstredaktilon kaj fermas la dialogon", + "app.captions.menu.select": "Elektu disponeblan lingvon", + "app.captions.menu.ariaSelect": "Lingvo de subtekstoj", + "app.captions.menu.subtitle": "Bonvolu elekti lingvon kaj stilon por subtekstoj en via seanco.", + "app.captions.menu.title": "Subtekstoj", + "app.captions.menu.fontSize": "Grando", + "app.captions.menu.fontColor": "Tekstkoloro", + "app.captions.menu.fontFamily": "Tiparo", + "app.captions.menu.backgroundColor": "Fonkoloro", + "app.captions.menu.previewLabel": "AntaÅmontraĵo", + "app.captions.menu.cancelLabel": "Nuligi", + "app.captions.pad.hide": "KaÅi subtekstojn", + "app.captions.pad.tip": "Premu Esc por enfokusigi la redaktilan ilobreton", + "app.captions.pad.ownership": "Transpreni", + "app.captions.pad.ownershipTooltip": "Vi fariÄos posedanto de {0} subtekstoj", + "app.captions.pad.interimResult": "Intertempaj rezultoj", + "app.captions.pad.dictationStart": "Komenci dikti", + "app.captions.pad.dictationStop": "Ĉesi dikti", + "app.captions.pad.dictationOnDesc": "Åœaltas parolrekonon", + "app.captions.pad.dictationOffDesc": "MalÅaltas parolrekonon", + "app.note.title": "Komunigitaj notoj", + "app.note.label": "Noto", + "app.note.hideNoteLabel": "KaÅi noton", + "app.note.tipLabel": "Premu Esc por enfokusigi la redaktilan ilobreton", + "app.user.activityCheck": "Kontroli aktivecon de uzanto", + "app.user.activityCheck.label": "Kontroli, ĉu uzanto ankoraÅ estas en kunsido ({0})", + "app.user.activityCheck.check": "Kontroli", + "app.userList.usersTitle": "Uzantoj", + "app.userList.participantsTitle": "Partoprenantoj", + "app.userList.messagesTitle": "MesaÄoj", + "app.userList.notesTitle": "Notoj", + "app.userList.notesListItem.unreadContent": "Nova enhavo estas disponebla en la sekcio pri komunigitaj notoj", + "app.userList.captionsTitle": "Subtekstoj", + "app.userList.presenter": "Prezentanto", + "app.userList.you": "Vi", + "app.userList.locked": "Åœlosite", + "app.userList.byModerator": "de (Administranto)", + "app.userList.label": "Listo de uzantoj", + "app.userList.toggleCompactView.label": "(Mal)Åaltas kompaktan vidreÄimon", + "app.userList.guest": "Gasto", + "app.userList.menuTitleContext": "Disponeblaj opcioj", + "app.userList.chatListItem.unreadSingular": "{0} Nova MesaÄo", + "app.userList.chatListItem.unreadPlural": "{0} Novaj MesaÄoj", + "app.userList.menu.chat.label": "Komenci privatan babiladon", + "app.userList.menu.clearStatus.label": "ViÅi staton", + "app.userList.menu.removeUser.label": "Forigi uzanton", + "app.userList.menu.removeConfirmation.label": "Forigi uzanton ({0})", + "app.userlist.menu.removeConfirmation.desc": "Preventas, ke ĉi tiu uzanto povu reeniri la seancon.", + "app.userList.menu.muteUserAudio.label": "Silentigi uzanton", + "app.userList.menu.unmuteUserAudio.label": "Malsilentigi uzanton", + "app.userList.userAriaLabel": "{0} {1} {2} Stato {3}", + "app.userList.menu.promoteUser.label": "Promocii al administranto", + "app.userList.menu.demoteUser.label": "Malpromocii al spektanto", + "app.userList.menu.unlockUser.label": "MalÅlosi {0}", + "app.userList.menu.lockUser.label": "Åœlosi {0}", + "app.userList.menu.directoryLookup.label": "Informa Serĉado", + "app.userList.menu.makePresenter.label": "Fari prezentanto", + "app.userList.userOptions.manageUsersLabel": "Administri uzantojn", + "app.userList.userOptions.muteAllLabel": "Silentigi ĉiujn uzantojn", + "app.userList.userOptions.muteAllDesc": "Silentigas ĉiujn uzantojn en la kunsido", + "app.userList.userOptions.clearAllLabel": "ViÅi ĉiujn statpiktogramojn", + "app.userList.userOptions.clearAllDesc": "ViÅas ĉiujn statpiktogramojn de la uzantoj", + "app.userList.userOptions.muteAllExceptPresenterLabel": "Silentigi ĉiujn uzantojn krom la prezentanton", + "app.userList.userOptions.muteAllExceptPresenterDesc": "Silentigas ĉiujn uzantojn en la kunsido escepte de la prezentanto", + "app.userList.userOptions.unmuteAllLabel": "MalÅalti kunsidan silentigadon", + "app.userList.userOptions.unmuteAllDesc": "Malsilentigas la kunsidon", + "app.userList.userOptions.lockViewersLabel": "Limigi rajtojn de spektantoj", + "app.userList.userOptions.lockViewersDesc": "Limigas certajn funkciojn de ĉeestantoj de la kunsido", + "app.userList.userOptions.connectionStatusLabel": "Konektostato", + "app.userList.userOptions.connectionStatusDesc": "Rigardi la konektostaton de uzantoj", + "app.userList.userOptions.disableCam": "La kameraoj de spektantoj estas malebligitaj", + "app.userList.userOptions.disableMic": "La mikrofonoj de spektantoj estas malebligitaj", + "app.userList.userOptions.disablePrivChat": "Privata babilado estas malebligita", + "app.userList.userOptions.disablePubChat": "Publika babilado estas malebligita", + "app.userList.userOptions.disableNote": "Komunigitaj notoj estas nun malebligitaj", + "app.userList.userOptions.hideUserList": "La listo de uzantoj estas nun kaÅita al spektantoj", + "app.userList.userOptions.webcamsOnlyForModerator": "Nur administrantoj povas vidi la kameraojn de spektantoj (pro Ålosaj agordoj)", + "app.userList.content.participants.options.clearedStatus": "La stato de ĉiuj uzantoj estas viÅita", + "app.userList.userOptions.enableCam": "La kameraoj de spektantoj estas ebligitaj", + "app.userList.userOptions.enableMic": "La mikrofonoj de spektantoj estas ebligitaj", + "app.userList.userOptions.enablePrivChat": "Privata babilado estas ebligita", + "app.userList.userOptions.enablePubChat": "Publika babilado estas ebligita", + "app.userList.userOptions.enableNote": "Komunigitaj notoj estas nun ebligitaj", + "app.userList.userOptions.showUserList": "La listo de uzantoj nun videblas al spektantoj", + "app.userList.userOptions.enableOnlyModeratorWebcam": "Vi nun povas Åalti vian kameraon; vi videblos al ĉiuj", + "app.media.label": "AÅdvidaĵoj", + "app.media.autoplayAlertDesc": "Permesi aliron", + "app.media.screenshare.start": "Ekrankundivido komenciÄis", + "app.media.screenshare.end": "Ekrankundivido finiÄis", + "app.media.screenshare.unavailable": "Ekrankundivido ne estas disponebla", + "app.media.screenshare.notSupported": "Via retumilo ne subtenas ekrankundividon.", + "app.media.screenshare.autoplayBlockedDesc": "Ni bezonas vian permeson por montri al vi la ekranon de la prezentanto.", + "app.media.screenshare.autoplayAllowLabel": "Rigardi kundividatan ekranon", + "app.screenshare.notAllowed": "Eraro: Vi ne ricevis permeson aliri la ekranon.", + "app.screenshare.notSupportedError": "Eraro: Ekrankundivido estas permesata nur sur sekuraj (SSL) domajnoj", + "app.screenshare.notReadableError": "Eraro: Registri vian ekranon malsukcesis", + "app.screenshare.genericError": "Eraro: Okazis eraro pri kundividado de ekrano. Bonvolu reprovi", + "app.meeting.ended": "Ĉi tiu seanco finiÄis", + "app.meeting.meetingTimeRemaining": "Restanta tempo de la kunsido: {0}", + "app.meeting.meetingTimeHasEnded": "Ne plu restas tempo. La kunsido baldaÅ fermiÄos", + "app.meeting.endedMessage": "Vi estos resendata al la komenca ekrano", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "La kunsido fermiÄos post minuto.", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "La kunsido fermiÄos post {0} minutoj.", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "Apartigitado fermiÄos post {0} minuto.", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "Apartigitado fermiÄos post unu minuto.", + "app.presentation.hide": "KaÅi prezentaĵon", + "app.presentation.notificationLabel": "Nuna prezentaĵo", + "app.presentation.slideContent": "Lumbilda enhavo", + "app.presentation.startSlideContent": "Komenco de lumbilda enhavo", + "app.presentation.endSlideContent": "Fino de lumbilda enhavo", + "app.presentation.emptySlideContent": "Neniu enhavo por nuna lumbildo", + "app.presentation.presentationToolbar.noNextSlideDesc": "Fino de prezentaĵo", + "app.presentation.presentationToolbar.noPrevSlideDesc": "Komenco de prezentaĵo", + "app.presentation.presentationToolbar.selectLabel": "Elektu lumbildon", + "app.presentation.presentationToolbar.prevSlideLabel": "AntaÅa lumbildo", + "app.presentation.presentationToolbar.prevSlideDesc": "ÅœanÄas la prezentaĵon al la antaÅa lumbildo", + "app.presentation.presentationToolbar.nextSlideLabel": "Sekva lumbildo", + "app.presentation.presentationToolbar.nextSlideDesc": "ÅœanÄas la prezentaĵon al la sekva lumbildo", + "app.presentation.presentationToolbar.skipSlideLabel": "Pretersalti al lumbildo", + "app.presentation.presentationToolbar.skipSlideDesc": "ÅœanÄas la prezentaĵon al specifa lumbildo", + "app.presentation.presentationToolbar.fitWidthLabel": "Adapti laÅ larÄo", + "app.presentation.presentationToolbar.fitWidthDesc": "Vidigas la tutan larÄon de la lumbildo", + "app.presentation.presentationToolbar.fitScreenLabel": "Adapti al ekrano", + "app.presentation.presentationToolbar.fitScreenDesc": "Vidigas la tutan lumbildon", + "app.presentation.presentationToolbar.zoomLabel": "Zomo", + "app.presentation.presentationToolbar.zoomDesc": "ÅœanÄas la zomnivelon de la prezentaĵo", + "app.presentation.presentationToolbar.zoomInLabel": "Zomi", + "app.presentation.presentationToolbar.zoomInDesc": "Zomas al la prezentaĵo", + "app.presentation.presentationToolbar.zoomOutLabel": "Malzomi", + "app.presentation.presentationToolbar.zoomOutDesc": "Malzomas de la prezentaĵo", + "app.presentation.presentationToolbar.zoomReset": "DefaÅltigi zomnivelon", + "app.presentation.presentationToolbar.zoomIndicator": "Nuna zom-elcentaĵo", + "app.presentation.presentationToolbar.fitToWidth": "Adapti laÅ larÄo", + "app.presentation.presentationToolbar.fitToPage": "Adapti al paÄo", + "app.presentation.presentationToolbar.goToSlide": "Lumbildo {0}", + "app.presentationUploder.title": "Prezentaĵo", + "app.presentationUploder.message": "Estante prezentanto, vi havas la kapablon alÅuti ajnajn Office-dokumenton aÅ PDF-dosieron. Ni rekomendas PDF por la plej bona rezulto. Bonvolu certigi, ke prezentaĵo estu elektita per la cirkla elektbutono dekstre.", + "app.presentationUploder.uploadLabel": "AlÅuti", + "app.presentationUploder.confirmLabel": "Konfirmi", + "app.presentationUploder.confirmDesc": "Konservas viajn ÅanÄojn kaj komencas la prezentaĵon", + "app.presentationUploder.dismissLabel": "Nuligi", + "app.presentationUploder.dismissDesc": "Fermas la dialogon kaj forigas viajn ÅanÄojn", + "app.presentationUploder.dropzoneLabel": "Åœovu dosierojn ĉi tien por alÅuti", + "app.presentationUploder.dropzoneImagesLabel": "Åœovu bildojn ĉi tien por alÅuti", + "app.presentationUploder.browseFilesLabel": "aÅ foliumu dosierojn", + "app.presentationUploder.browseImagesLabel": "aÅ foliumu/fotu bildojn", + "app.presentationUploder.fileToUpload": "AlÅutota ...", + "app.presentationUploder.currentBadge": "Nuna", + "app.presentationUploder.rejectedError": "La elektita(j) dosiero(j) estis rifuzita(j). Bonvolu kontroli la dosierspeco(j)n.", + "app.presentationUploder.upload.progress": "AlÅutante ({0}%)", + "app.presentationUploder.upload.413": "La dosiero estas tro granda. Bonvolu disigi Äin en plurajn dosierojn.", + "app.presentationUploder.genericError": "Opus, io malsukcesis...", + "app.presentationUploder.upload.408": "Tempolimo de peto pri alÅuta ĵetono.", + "app.presentationUploder.upload.404": "404: Nevalida alÅuta ĵetono", + "app.presentationUploder.upload.401": "Malsukcesis peto pri prezenta alÅuta ĵetono.", + "app.presentationUploder.conversion.conversionProcessingSlides": "Traktante paÄon {0} el {1}", + "app.presentationUploder.conversion.genericConversionStatus": "Konvertante dosieron ...", + "app.presentationUploder.conversion.generatingThumbnail": "Generante miniaturojn ...", + "app.presentationUploder.conversion.generatedSlides": "Lumbildoj generiÄis ...", + "app.presentationUploder.conversion.generatingSvg": "Generante SVG-bildojn ...", + "app.presentationUploder.conversion.pageCountExceeded": "Estas tro da paÄoj. Bonvolu disigi la dosieron en plurajn dosierojn.", + "app.presentationUploder.conversion.officeDocConversionInvalid": "Malsukcesis trakti Office-dokumenton. Bonvolu anstataÅe alÅuti PDF.", + "app.presentationUploder.conversion.officeDocConversionFailed": "Malsukcesis trakti Office-dokumenton. Bonvolu anstataÅe alÅuti PDF.", + "app.presentationUploder.conversion.pdfHasBigPage": "Ni ne povis konverti la PDF-dosieron, bonvolu provi optimumigi Äin", + "app.presentationUploder.conversion.timeout": "Ups, la konvertado daÅris tro longe", + "app.presentationUploder.conversion.pageCountFailed": "Malsukcesis determini la kvanton de paÄoj.", + "app.presentationUploder.isDownloadableLabel": "Malpermesi elÅutadon de la prezentaĵo", + "app.presentationUploder.isNotDownloadableLabel": "Permesi elÅutadon de la prezentaĵo", + "app.presentationUploder.removePresentationLabel": "Forigi la prezentaĵon", + "app.presentationUploder.setAsCurrentPresentation": "Igi la prezentaĵon la nuna", + "app.presentationUploder.tableHeading.filename": "Dosiernomo", + "app.presentationUploder.tableHeading.options": "Opcioj", + "app.presentationUploder.tableHeading.status": "Stato", + "app.presentationUploder.uploading": "AlÅutante {0} {1}", + "app.presentationUploder.uploadStatus": "{0} de {1} alÅutadoj kompletaj", + "app.presentationUploder.completed": "{0} alÅutadoj kompletaj", + "app.presentationUploder.item" : "ero", + "app.presentationUploder.itemPlural" : "eroj", + "app.presentationUploder.clearErrors": "ViÅi erarojn", + "app.presentationUploder.clearErrorsDesc": "ViÅas malsukcesintajn alÅutojn de prezentaĵoj", + "app.poll.pollPaneTitle": "Enketado", + "app.poll.quickPollTitle": "Rapida enketo", + "app.poll.hidePollDesc": "KaÅas la enketmenuon", + "app.poll.customPollInstruction": "Por krei memfaritan enketon, elektu la suban butonon kaj enmetu viajn opciojn.", + "app.poll.quickPollInstruction": "Elektu opcion sube por komenci vian enketon.", + "app.poll.customPollLabel": "Memfarita enketo", + "app.poll.startCustomLabel": "Komenci memfaritan enketon", + "app.poll.activePollInstruction": "Lasu ĉi tiun panelon malfermita por vidi realtempajn respondojn al via enketo. Kiam vi pretas, elektu 'Publikigi enketrezultojn' por publikigi la rezultojn kaj fini la enketon.", + "app.poll.publishLabel": "Publikigi enketrezultojn", + "app.poll.backLabel": "Reen al enketopcioj", + "app.poll.closeLabel": "Fermi", + "app.poll.waitingLabel": "Atendante respondojn ({0}/{1})", + "app.poll.ariaInputCount": "Opcio {0} el {1} de memfarita enketo", + "app.poll.customPlaceholder": "Aldoni enketopcion", + "app.poll.noPresentationSelected": "Neniu prezentaĵo elektita! Bonvolu elekti unu.", + "app.poll.clickHereToSelect": "Klaku ĉi tien por elekti", + "app.poll.t": "Vera", + "app.poll.f": "Malvera", + "app.poll.tf": "Vera / Malvera", + "app.poll.y": "Jes", + "app.poll.n": "Ne", + "app.poll.yn": "Jes / Ne", + "app.poll.a2": "A / B", + "app.poll.a3": "A / B / C", + "app.poll.a4": "A / B / C / D", + "app.poll.a5": "A / B / C / D / E", + "app.poll.answer.true": "Vera", + "app.poll.answer.false": "Malvera", + "app.poll.answer.yes": "Jes", + "app.poll.answer.no": "Ne", + "app.poll.answer.a": "A", + "app.poll.answer.b": "B", + "app.poll.answer.c": "C", + "app.poll.answer.d": "D", + "app.poll.answer.e": "E", + "app.poll.liveResult.usersTitle": "Uzantoj", + "app.poll.liveResult.responsesTitle": "Respondo", + "app.polling.pollingTitle": "Enketopcioj", + "app.polling.pollAnswerLabel": "Enketrespondo {0}", + "app.polling.pollAnswerDesc": "Elektu ĉi tiun opcion por voĉdoni por {0}", + "app.failedMessage": "Pardonon, okazis problemo konektiÄi kun la servilo.", + "app.downloadPresentationButton.label": "ElÅuti la originalan prezentaĵon", + "app.connectingMessage": "KonektiÄante ...", + "app.waitingMessage": "Malkonektite. Reprovos konektiÄi post {0} sekundo(j)n ...", + "app.retryNow": "Reprovi nun", + "app.muteWarning.label": "Klaku al {0} por malsilentigi vin.", + "app.navBar.settingsDropdown.optionsLabel": "Opcioj", + "app.navBar.settingsDropdown.fullscreenLabel": "Fari plenekrana", + "app.navBar.settingsDropdown.settingsLabel": "Agordoj", + "app.navBar.settingsDropdown.aboutLabel": "Pri", + "app.navBar.settingsDropdown.leaveSessionLabel": "Elsaluti", + "app.navBar.settingsDropdown.exitFullscreenLabel": "Eliri el plenekrana reÄimo", + "app.navBar.settingsDropdown.fullscreenDesc": "Fari la menuon de agordoj plenekrana", + "app.navBar.settingsDropdown.settingsDesc": "ÅœanÄi la Äeneralajn agordojn", + "app.navBar.settingsDropdown.aboutDesc": "Montri informojn pri la kliento", + "app.navBar.settingsDropdown.leaveSessionDesc": "Forlasi la kunsidon", + "app.navBar.settingsDropdown.exitFullscreenDesc": "Eliri el plenekrana reÄimo", + "app.navBar.settingsDropdown.hotkeysLabel": "Fulmklavoj", + "app.navBar.settingsDropdown.hotkeysDesc": "Listo de disponeblaj fulmklavoj", + "app.navBar.settingsDropdown.helpLabel": "Helpo", + "app.navBar.settingsDropdown.helpDesc": "Sendas la uzanton al filmetaj instruiloj (malfermas novan langeton)", + "app.navBar.settingsDropdown.endMeetingDesc": "Finas la nunan kunsidon", + "app.navBar.settingsDropdown.endMeetingLabel": "Fini kunsidon", + "app.navBar.userListToggleBtnLabel": "KaÅilo por listo de uzantoj", + "app.navBar.toggleUserList.ariaLabel": "KaÅilo por uzantoj kaj mesaÄoj", + "app.navBar.toggleUserList.newMessages": "kun sciigo pri novaj mesaÄoj", + "app.navBar.recording": "Ĉi tiu seanco estas registrata", + "app.navBar.recording.on": "Registrante", + "app.navBar.recording.off": "Neregistrante", + "app.navBar.emptyAudioBrdige": "Neniu Åaltita mikrofono. Kundividu vian mikrofonon por aldoni sonon al ĉi tiu registraĵo.", + "app.leaveConfirmation.confirmLabel": "Forlasi", + "app.leaveConfirmation.confirmDesc": "Elsalutigas vin el la kunsido", + "app.endMeeting.title": "Fini kunsidon", + "app.endMeeting.description": "Ĉu vi certas, ke vi volas fini ĉi tiun seancon?", + "app.endMeeting.yesLabel": "Jes", + "app.endMeeting.noLabel": "Ne", + "app.about.title": "Pri", + "app.about.version": "Klienta versio:", + "app.about.copyright": "Kopirajto:", + "app.about.confirmLabel": "Bone", + "app.about.confirmDesc": "Indikas vian konfirmon", + "app.about.dismissLabel": "Nuligi", + "app.about.dismissDesc": "Fermi priklientajn informojn", + "app.actionsBar.changeStatusLabel": "ÅœanÄi staton", + "app.actionsBar.muteLabel": "Silentigi", + "app.actionsBar.unmuteLabel": "Malsilentigi", + "app.actionsBar.camOffLabel": "Kamerao malÅaltita", + "app.actionsBar.raiseLabel": "Levi", + "app.actionsBar.label": "Agbreto", + "app.actionsBar.actionsDropdown.restorePresentationLabel": "Remontri prezentaĵon", + "app.actionsBar.actionsDropdown.restorePresentationDesc": "Butono por remontri la prezentaĵon fermitan", + "app.screenshare.screenShareLabel" : "Ekrankundivido", + "app.submenu.application.applicationSectionTitle": "Aplikaĵo", + "app.submenu.application.animationsLabel": "Animacioj", + "app.submenu.application.fontSizeControlLabel": "Grando de la tiparo", + "app.submenu.application.increaseFontBtnLabel": "Pligrandigi la tiparon de la aplikaĵo", + "app.submenu.application.decreaseFontBtnLabel": "Malpligrandigi la tiparon de la aplikaĵo", + "app.submenu.application.currentSize": "Nune {0}", + "app.submenu.application.languageLabel": "Lingvo de la aplikaĵo", + "app.submenu.application.languageOptionLabel": "Elekti lingvon", + "app.submenu.application.noLocaleOptionLabel": "Neniuj elektitaj lokaĵaroj", + "app.submenu.notification.SectionTitle": "Sciigoj", + "app.submenu.notification.Desc": "Difinu kiel kaj pri kio vi estos sciigata.", + "app.submenu.notification.audioAlertLabel": "Sonaj sciigoj", + "app.submenu.notification.pushAlertLabel": "Åœprucsciigoj", + "app.submenu.notification.messagesLabel": "BabilmesaÄo", + "app.submenu.notification.userJoinLabel": "Alveno de uzanto", + "app.submenu.audio.micSourceLabel": "Fonto de mikrofono", + "app.submenu.audio.speakerSourceLabel": "Fonto de laÅtparolilo", + "app.submenu.audio.streamVolumeLabel": "La laÅteco de via sona fluo", + "app.submenu.video.title": "Video", + "app.submenu.video.videoSourceLabel": "Fonto de video", + "app.submenu.video.videoOptionLabel": "Elekti la fonton de video", + "app.submenu.video.videoQualityLabel": "Kvalito de video", + "app.submenu.video.qualityOptionLabel": "Elekti la kvaliton de video", + "app.submenu.video.participantsCamLabel": "Spektante la kameraojn de partoprenantoj", + "app.settings.applicationTab.label": "Aplikaĵo", + "app.settings.audioTab.label": "Sono", + "app.settings.videoTab.label": "Video", + "app.settings.usersTab.label": "Partoprenantoj", + "app.settings.main.label": "Agordoj", + "app.settings.main.cancel.label": "Nuligi", + "app.settings.main.cancel.label.description": "Forĵetas la ÅanÄojn kaj fermas la menuon de agordoj", + "app.settings.main.save.label": "Konservi", + "app.settings.main.save.label.description": "Konservas la ÅanÄojn kaj fermas la menuon de agordoj", + "app.settings.dataSavingTab.label": "DatumÅparoj", + "app.settings.dataSavingTab.webcam": "Ebligi kameraojn", + "app.settings.dataSavingTab.screenShare": "Ebligi ekrankundividon", + "app.settings.dataSavingTab.description": "Por Åpari rettrafikon, agordu tion, kio montriÄu.", + "app.settings.save-notification.label": "La agordoj konserviÄis", + "app.statusNotifier.raisedHandDesc": "{0} levis manon", + "app.statusNotifier.and": "kaj", + "app.switch.onLabel": "JES", + "app.switch.offLabel": "NE", + "app.talkingIndicator.ariaMuteDesc" : "Elekti por silentigi uzanton", + "app.talkingIndicator.isTalking" : "{0} parolas", + "app.talkingIndicator.wasTalking" : "{0} ĉesis paroli", + "app.actionsBar.actionsDropdown.actionsLabel": "Agoj", + "app.actionsBar.actionsDropdown.presentationLabel": "AlÅuti prezentaĵon", + "app.actionsBar.actionsDropdown.initPollLabel": "Iniciati enketon", + "app.actionsBar.actionsDropdown.desktopShareLabel": "Kundividi vian ekranon", + "app.actionsBar.actionsDropdown.lockedDesktopShareLabel": "Ekrankundivido malebligita", + "app.actionsBar.actionsDropdown.stopDesktopShareLabel": "Ĉesi kundividi vian ekranon", + "app.actionsBar.actionsDropdown.presentationDesc": "AlÅuti vian prezentaĵon", + "app.actionsBar.actionsDropdown.initPollDesc": "Komenci enketon", + "app.actionsBar.actionsDropdown.desktopShareDesc": "Kundividi vian ekranon kun aliaj", + "app.actionsBar.actionsDropdown.stopDesktopShareDesc": "Ĉesi kundividi vian ekranon kun aliaj", + "app.actionsBar.actionsDropdown.pollBtnLabel": "Komenci enketon", + "app.actionsBar.actionsDropdown.pollBtnDesc": "(Mal)kaÅas la panelon pri enketo", + "app.actionsBar.actionsDropdown.saveUserNames": "Konservi uzantnomojn", + "app.actionsBar.actionsDropdown.createBreakoutRoom": "Krei apartigitajn ĉambrojn", + "app.actionsBar.actionsDropdown.createBreakoutRoomDesc": "Krei apartigitajn ĉambrojn por disigi la nunan kunsidon", + "app.actionsBar.actionsDropdown.captionsLabel": "Verki subtekstojn", + "app.actionsBar.actionsDropdown.captionsDesc": "(Mal)kaÅas la panelon pri subtekstoj", + "app.actionsBar.actionsDropdown.takePresenter": "IÄi prezentanto", + "app.actionsBar.actionsDropdown.takePresenterDesc": "Faras vin la nova prezentanto", + "app.actionsBar.emojiMenu.statusTriggerLabel": "ÅœanÄi staton", + "app.actionsBar.emojiMenu.awayLabel": "For", + "app.actionsBar.emojiMenu.awayDesc": "ÅœanÄas vian staton al malĉeesta", + "app.actionsBar.emojiMenu.raiseHandLabel": "Levi", + "app.actionsBar.emojiMenu.raiseHandDesc": "Levas vian manon por starigi demandon", + "app.actionsBar.emojiMenu.neutralLabel": "Sendecida", + "app.actionsBar.emojiMenu.neutralDesc": "Åœangas vian staton al sendecida", + "app.actionsBar.emojiMenu.confusedLabel": "Konfuzita", + "app.actionsBar.emojiMenu.confusedDesc": "ÅœanÄas vian staton al konfuzita", + "app.actionsBar.emojiMenu.sadLabel": "MalÄoja", + "app.actionsBar.emojiMenu.sadDesc": "ÅœanÄas vian staton al malÄoja", + "app.actionsBar.emojiMenu.happyLabel": "Äœoja", + "app.actionsBar.emojiMenu.happyDesc": "ÅœanÄas vian staton al Äoja", + "app.actionsBar.emojiMenu.noneLabel": "ViÅi staton", + "app.actionsBar.emojiMenu.noneDesc": "ViÅas vian staton", + "app.actionsBar.emojiMenu.applauseLabel": "AplaÅdanta", + "app.actionsBar.emojiMenu.applauseDesc": "ÅœanÄas vian staton al aplaÅdanta", + "app.actionsBar.emojiMenu.thumbsUpLabel": "Aprobanta", + "app.actionsBar.emojiMenu.thumbsUpDesc": "ÅœanÄas vian staton al aprobanta", + "app.actionsBar.emojiMenu.thumbsDownLabel": "Malaprobanta", + "app.actionsBar.emojiMenu.thumbsDownDesc": "ÅœanÄas vian staton al malaprobanta", + "app.actionsBar.currentStatusDesc": "aktuala stato {0}", + "app.actionsBar.captions.start": "Komenci rigardi subtekstojn", + "app.actionsBar.captions.stop": "Ĉesi rigardi subtekstojn", + "app.audioNotification.audioFailedError1001": "WebSocket-konekto nekonektiÄis (eraro 1001)", + "app.audioNotification.audioFailedError1002": "Ne povis starigi WebSocket-konekton (eraro 1002)", + "app.audioNotification.audioFailedError1003": "Ĉi tiu versio de via retumilo ne estas subtenata (eraro 1003)", + "app.audioNotification.audioFailedError1004": "Malsukceso dum voko (kialo={0}) (eraro 1004)", + "app.audioNotification.audioFailedError1005": "Voko finiÄis neatendite (eraro 1005)", + "app.audioNotification.audioFailedError1006": "Atingis tempolimon por voko (eraro 1006)", + "app.audioNotification.audioFailedError1007": "Malsuskceso pri konekto (ICE-eraro 1007)", + "app.audioNotification.audioFailedError1008": "Transigo malsukcesis (eraro 1008)", + "app.audioNotification.audioFailedError1009": "Ne povis venigi informojn pri STUN/TURN-servilo (eraro 1009)", + "app.audioNotification.audioFailedError1010": "Atingis tempolimon por konektnegoco (ICE-eraro 1010)", + "app.audioNotification.audioFailedError1011": "Atingis tempolimon por konektiÄo (ICE-eraro 1011)", + "app.audioNotification.audioFailedError1012": "Konekto fermita (ICE-eraro 1012)", + "app.audioNotification.audioFailedMessage": "Via sonkonekto ne sukcesis konektiÄi", + "app.audioNotification.mediaFailedMessage": "getUserMicMedia malsukcesis, ĉar nur sekuraj originoj estas permesataj", + "app.audioNotification.closeLabel": "Fermi", + "app.audioNotificaion.reconnectingAsListenOnly": "Mikrofonoj estas malebligitaj por spektantoj, vi nun konektiÄas kiel nura aÅskultanto", + "app.breakoutJoinConfirmation.title": "Eniri apartigitan ĉambron", + "app.breakoutJoinConfirmation.message": "Ĉu vi volas eniri?", + "app.breakoutJoinConfirmation.confirmDesc": "Enirigas vin en la apartigitan ĉambron", + "app.breakoutJoinConfirmation.dismissLabel": "Nuligi", + "app.breakoutJoinConfirmation.dismissDesc": "Fermas kaj malakceptas eniri la apartigitan ĉambron", + "app.breakoutJoinConfirmation.freeJoinMessage": "Elektu apartigitan ĉambron por eniri", + "app.breakoutTimeRemainingMessage": "Restanta tempo por apartigita ĉambro: {0}", + "app.breakoutWillCloseMessage": "Ne plu restas tempo. La apartigita ĉambro baldaÅ fermiÄos", + "app.calculatingBreakoutTimeRemaining": "Kalkulante restantan tempon ...", + "app.audioModal.ariaTitle": "Dialogo pri sona konektiÄo", + "app.audioModal.microphoneLabel": "Mikrofono", + "app.audioModal.listenOnlyLabel": "Nur aÅskulti", + "app.audioModal.audioChoiceLabel": "Kiel vi volas konekti sonon?", + "app.audioModal.iOSBrowser": "Sono/Video ne subtenata", + "app.audioModal.iOSErrorDescription": "Nuntempte sono kaj video ne estas subtenataj de Chrome por iOS.", + "app.audioModal.iOSErrorRecommendation": "Ni rekomendas uzi Safari por iOS.", + "app.audioModal.audioChoiceDesc": "Elektu kiel konektiÄi kun la sono en ĉi tiu kunsido", + "app.audioModal.unsupportedBrowserLabel": "Åœajnas, ke vi uzas retumilon, kiun ni ne plene subtenas. Bonvolu uzi aÅ {0} aÅ {1} por plena subteno.", + "app.audioModal.closeLabel": "Fermi", + "app.audioModal.yes": "Jes", + "app.audioModal.no": "Ne", + "app.audioModal.yes.arialabel" : "EÄ¥o aÅdeblas", + "app.audioModal.no.arialabel" : "EÄ¥o ne aÅdeblas", + "app.audioModal.echoTestTitle": "Ĉi tio estas privata eÄ¥otesto. Diru kelkajn vortojn. Ĉu vi aÅdis sonon?", + "app.audioModal.settingsTitle": "ÅœanÄu viajn sonajn agordojn", + "app.audioModal.helpTitle": "Estis problemo kun via sonaparato", + "app.audioModal.helpText": "Ĉu vi donis permeson por aliro al via mikrofono? Rimarku, ke kiam vi provas konektiÄi kun la sono, aperu dialogo petanta permeson aliri vian sonaparaton. Bonvolu akcepti por konekti la sonon de la kunsido. Aliokaze, provu ÅanÄi la permesojn pri via mikrofono en la agordoj de via retumilo.", + "app.audioModal.help.noSSL": "Ĉi tiu paÄo ne estas sekura. Por permeso aliri mikrofonon nepras, ke la paÄo serviÄu trans HTTPS. Bonvolu kontakti la administranton de la servilo.", + "app.audioModal.help.macNotAllowed": "Åœajnas, ke la Sistemaj Agordoj de via MakintoÅo blokas aliron al via mikrofono. Malfermu: Sistemaj Agordoj > Sekureco kaj Privateco > Privateco > Mikrofono, kaj certigu, ke la retumilo, kiun vi uzas, estu elektita.", + "app.audioModal.audioDialTitle": "KonektiÄi per via telefono", + "app.audioDial.audioDialDescription": "Numerumu", + "app.audioDial.audioDialConfrenceText": "kaj enmetu la PIN-kodon de la kunsido:", + "app.audioModal.autoplayBlockedDesc": "Ni bezonas vian permeson ludigi sonon.", + "app.audioModal.playAudio": "Ludigi sonon", + "app.audioModal.playAudio.arialabel" : "Ludigi sonon", + "app.audioDial.tipIndicator": "Helpeto", + "app.audioDial.tipMessage": "Premu la '0'-klavon sur via telefono por (mal)silentigi vin mem.", + "app.audioModal.connecting": "KonektiÄante", + "app.audioModal.connectingEchoTest": "KonektiÄante kun eÄ¥otesto", + "app.audioManager.joinedAudio": "Vi konektiÄis kun la sono de la kunsido", + "app.audioManager.joinedEcho": "Vi konektiÄis kun la eÄ¥otesto", + "app.audioManager.leftAudio": "Vi forlasis la sonon de la kunsido", + "app.audioManager.reconnectingAudio": "Provante rekonektiÄi kun la sono", + "app.audioManager.genericError": "Eraro: Okazis eraro, bonvolu reprovi", + "app.audioManager.connectionError": "Eraro: konekta eraro", + "app.audioManager.requestTimeout": "Eraro: Atingis tempolimon dum la peto", + "app.audioManager.invalidTarget": "Eraro: Provis peti ion de nevalida celo", + "app.audioManager.mediaError": "Eraro: Okazis problemo akiri viajn sonaparatojn", + "app.audio.joinAudio": "KonektiÄi kun sono", + "app.audio.leaveAudio": "Forlasi sonon", + "app.audio.enterSessionLabel": "Eniri seacon", + "app.audio.playSoundLabel": "Ludigi sonon", + "app.audio.backLabel": "Reen", + "app.audio.audioSettings.titleLabel": "Elektu viajn sonagordojn", + "app.audio.audioSettings.descriptionLabel": "Bonvolu rimarki, ke dialogo aperos en via retumilo, postulante, ke vi akceptu kundividi vian mikrofonon.", + "app.audio.audioSettings.microphoneSourceLabel": "Fonto de mikrofono", + "app.audio.audioSettings.speakerSourceLabel": "Fonto de laÅtparolilo", + "app.audio.audioSettings.microphoneStreamLabel": "La laÅteco de via sona fluo", + "app.audio.audioSettings.retryLabel": "Reprovi", + "app.audio.listenOnly.backLabel": "Reen", + "app.audio.listenOnly.closeLabel": "Fermi", + "app.audio.permissionsOverlay.title": "Permesi aliron al vi mikrofono", + "app.audio.permissionsOverlay.hint": "Ni bezonas permeson uzi viajn sonaparaton, por ke vi povu konektiÄi kun la voĉa kunsido :)", + "app.error.removed": "Vi estis forigita de la kunsido", + "app.error.meeting.ended": "Vi elsalutis el la kunsido", + "app.meeting.logout.duplicateUserEjectReason": "Duobla uzanto provas eniri la kunsidon", + "app.meeting.logout.permissionEjectReason": "Forĵetite pro malobservo de permesoj", + "app.meeting.logout.ejectedFromMeeting": "Vi estis forigita de la kunsido", + "app.meeting.logout.validateTokenFailedEjectReason": "Malsukcesis validigi rajtigan ĵetonon", + "app.meeting.logout.userInactivityEjectReason": "Uzanto malaktivis dum tro longa tempo", + "app.meeting-ended.rating.legendLabel": "Pritakso", + "app.meeting-ended.rating.starLabel": "Stelo", + "app.modal.close": "Fermi", + "app.modal.close.description": "Ignoras ÅanÄojn kaj fermas la dialogon", + "app.modal.confirm": "Prete", + "app.modal.newTab": "(malfermas novan langeton)", + "app.modal.confirm.description": "Konservas ÅanÄojn kaj fermas la dialogon", + "app.dropdown.close": "Fermi", + "app.error.400": "Mispeto", + "app.error.401": "Nerajtigite", + "app.error.403": "Vi estis forigita de la kunsido", + "app.error.404": "Netrovite", + "app.error.410": "La kunside jam finiÄis", + "app.error.500": "Ups, io fuÅiÄis", + "app.error.leaveLabel": "Re-ensalutu", + "app.error.fallback.presentation.title": "Okazis eraro", + "app.error.fallback.presentation.description": "Äœi protokoliÄis. Bonvolu provi reÅargi la paÄon.", + "app.error.fallback.presentation.reloadButton": "ReÅargi", + "app.guest.waiting": "Atendante aprobon eniri", + "app.userList.guest.waitingUsers": "Atendantaj Uzantoj", + "app.userList.guest.waitingUsersTitle": "Administrado de uzantoj", + "app.userList.guest.optionTitle": "Pritaksi Traktatajn Uzantojn", + "app.userList.guest.allowAllAuthenticated": "Akcepti ĉiujn aÅtentigitojn", + "app.userList.guest.allowAllGuests": "Akcepti ĉiujn gastojn", + "app.userList.guest.allowEveryone": "Akcepti ĉiujn", + "app.userList.guest.denyEveryone": "Rifuzi ĉiujn", + "app.userList.guest.pendingUsers": "{0} Traktataj Uzantoj", + "app.userList.guest.pendingGuestUsers": "{0} Traktataj Gastoj", + "app.userList.guest.pendingGuestAlert": "Eniris la seancon kaj atendas vian aprobon.", + "app.userList.guest.rememberChoice": "Memori elekton", + "app.userList.guest.acceptLabel": "Akcepti", + "app.userList.guest.denyLabel": "Rifuzi", + "app.user-info.title": "Informa Serĉado", + "app.toast.breakoutRoomEnded": "La apartigita ĉambro finiÄis. Bonvolu rekonektiÄi kun la sono.", + "app.toast.chat.public": "Nova publika babilmesaÄo", + "app.toast.chat.private": "Nova privata babilmesaÄo", + "app.toast.chat.system": "Sistemo", + "app.toast.clearedEmoji.label": "ViÅis EmoÄian staton", + "app.toast.setEmoji.label": "ÅœanÄis EmoÄian staton al {0}", + "app.toast.meetingMuteOn.label": "Ĉiuj uzantoj estis silentigitaj", + "app.toast.meetingMuteOff.label": "MalÅaltis silentigadon de la kunsido", + "app.notification.recordingStart": "Ĉi tiu seanco nun estas registrata", + "app.notification.recordingStop": "Ĉi tiu seanco ne estas registrata", + "app.notification.recordingPaused": "Ĉi tiu seanco ne plu estas registrata", + "app.notification.recordingAriaLabel": "Registrotempo ", + "app.notification.userJoinPushAlert": "{0} eniris la seancon", + "app.submenu.notification.raiseHandLabel": "Levi manon", + "app.shortcut-help.title": "Fulmklavojn", + "app.shortcut-help.accessKeyNotAvailable": "Alirklavo ne disponeblas", + "app.shortcut-help.comboLabel": "Kombino", + "app.shortcut-help.functionLabel": "Funkcio", + "app.shortcut-help.closeLabel": "Fermi", + "app.shortcut-help.closeDesc": "Fermas la dialogon pri fulmklavoj", + "app.shortcut-help.openOptions": "Malfermi agordojn", + "app.shortcut-help.toggleUserList": "(Mal)kaÅi liston de uzantoj", + "app.shortcut-help.toggleMute": "(Mal)silentigi", + "app.shortcut-help.togglePublicChat": "(Mal)kaÅi Publikan Babilejon (Listo de Uzantoj estu malfermita)", + "app.shortcut-help.hidePrivateChat": "KaÅi privatan babilejon", + "app.shortcut-help.closePrivateChat": "Fermi privatan babilejon", + "app.shortcut-help.openActions": "Malfermi menuon pri agoj", + "app.shortcut-help.openStatus": "Malfermi menuon pri stato", + "app.shortcut-help.togglePan": "Aktivigi panoramilon (Prezentanto)", + "app.shortcut-help.nextSlideDesc": "Sekva lumbildo (Prezentanto)", + "app.shortcut-help.previousSlideDesc": "AntaÅa lumbildo (Prezentanto)", + "app.lock-viewers.title": "Limigi rajtojn de spektantoj", + "app.lock-viewers.description": "Ĉi tiuj agordoj ebligas al vi limigi la spektantojn uzi diversajn funkciojn.", + "app.lock-viewers.featuresLable": "Funkcio", + "app.lock-viewers.lockStatusLabel": "Stato", + "app.lock-viewers.webcamLabel": "Kundividi kameraon", + "app.lock-viewers.otherViewersWebcamLabel": "Vidi la kameraojn de aliaj spektantoj", + "app.lock-viewers.microphoneLable": "Kundividi mikrofonon", + "app.lock-viewers.PublicChatLabel": "Sendi publikajn babilmesaÄojn", + "app.lock-viewers.PrivateChatLable": "Sendi privatajn babilmesaÄojn", + "app.lock-viewers.notesLabel": "Redakti komunigitajn notojn", + "app.lock-viewers.userListLabel": "Vidi aliajn spektantojn en la Listo de Uzantoj", + "app.lock-viewers.ariaTitle": "Dialogo pri limigaj agordoj de spektantoj", + "app.lock-viewers.button.apply": "Apliki", + "app.lock-viewers.button.cancel": "Nuligi", + "app.lock-viewers.locked": "Malebligite", + "app.lock-viewers.unlocked": "Ebligite", + "app.connection-status.ariaTitle": "Dialogo pri konektostato", + "app.connection-status.title": "Konektostato", + "app.connection-status.description": "Vidi la konektostaton de uzantoj", + "app.connection-status.empty": "Neniuj konektaj problemoj raportiÄis Äis nun", + "app.connection-status.more": "pli", + "app.connection-status.offline": "Nekonektite", + "app.recording.startTitle": "Komenci registradon", + "app.recording.stopTitle": "PaÅzigi registradon", + "app.recording.resumeTitle": "DaÅrigi registradon", + "app.recording.startDescription": "Vi povos poste alklaki la registrobutonon por paÅzigi la registradon.", + "app.recording.stopDescription": "Ĉu vi certas, ke vi volas paÅzigi la registradon? Vi povos daÅrigi la registradon per alklako de la registrobutono.", + "app.videoPreview.cameraLabel": "Kamerao", + "app.videoPreview.profileLabel": "Kvalito", + "app.videoPreview.cancelLabel": "Nuligi", + "app.videoPreview.closeLabel": "Fermi", + "app.videoPreview.findingWebcamsLabel": "Trovante kameraojn", + "app.videoPreview.startSharingLabel": "Komenci kundividi", + "app.videoPreview.stopSharingLabel": "Ĉesi kundividon", + "app.videoPreview.stopSharingAllLabel": "Ĉesi ĉion", + "app.videoPreview.sharedCameraLabel": "Ĉi tiu kamerao jam estas kundividata", + "app.videoPreview.webcamOptionLabel": "Elektu kameraon", + "app.videoPreview.webcamPreviewLabel": "AntaÅrigardo de la kamerao", + "app.videoPreview.webcamSettingsTitle": "Kameraaj agordoj", + "app.videoPreview.webcamNotFoundLabel": "Ne trovis kameraon", + "app.videoPreview.profileNotFoundLabel": "Neniu subtenata kameraprofilo", + "app.video.joinVideo": "Kundividi kameraon", + "app.video.leaveVideo": "Ĉesi kundividi kameraon", + "app.video.iceCandidateError": "Eraro dum aldono de ICE-kandidato", + "app.video.iceConnectionStateError": "Malsuskceso pri konekto (ICE-eraro 1007)", + "app.video.permissionError": "Eraro kundividi kameraon. Bonvolu kontroli permesojn", + "app.video.sharingError": "Eraro kundividi kameraon", + "app.video.notFoundError": "Ne povis trovi kameraon. Bonvolu certigi, ke Äi estu konektita", + "app.video.notAllowed": "Mankas permeso por kundividi kameraon. Bonvolu kontroli la permesojn de via retumilo", + "app.video.notSupportedError": "Nur eblas kundividi kameraon kun sekuraj fontoj. CertiÄu, ke via SSL-atestilo validas", + "app.video.notReadableError": "Ne povis akiri videon de la kamerao. Bonvolu certigi, ke alia programo ne uzu la kameraon", + "app.video.mediaFlowTimeout1020": "AÅdvidaĵo ne atingis la servilon (eraro 1020)", + "app.video.suggestWebcamLock": "Ĉu efikigi limigajn agordojn por kameraoj de spektantoj?", + "app.video.suggestWebcamLockReason": "(ĉi tio plibonigos la stabilecon de la kunsido)", + "app.video.enable": "Ebligi", + "app.video.cancel": "Nuligi", + "app.video.swapCam": "InterÅanÄi", + "app.video.swapCamDesc": "InterÅanÄas direktojn de la kameraoj", + "app.video.videoLocked": "Kundivido de kameraoj malebligita", + "app.video.videoButtonDesc": "Kundividi kameraon", + "app.video.videoMenu": "Videomenuo", + "app.video.videoMenuDisabled": "Videomenuo de la kamerao estas malebligita en al agordoj", + "app.video.videoMenuDesc": "Malfermi video-falmenuon", + "app.video.chromeExtensionError": "Vi devas instali", + "app.video.chromeExtensionErrorLink": "ĉi tiun Chrome-aldonaĵon", + "app.fullscreenButton.label": "Fari {0} plenekrana", + "app.deskshare.iceConnectionStateError": "Konekto fiaskis dum ekrankundivido (ICE-eraro 1108)", + "app.sfu.mediaServerConnectionError2000": "Neeble konektiÄi kun aÅdvida servilo (eraro 2000)", + "app.sfu.mediaServerOffline2001": "AÅdvida servilo estas senkonekta. Bonvolu reprovi poste (eraro 2001)", + "app.sfu.mediaServerNoResources2002": "AÅdvida servilo ne havas disponeblajn rimedojn (eraro 2002)", + "app.sfu.mediaServerRequestTimeout2003": "Atingas tempolimojn ĉe petoj al la aÅdvida servilo (eraro 2003)", + "app.sfu.serverIceGatheringFailed2021": "AÅdvida servilo ne povas akiri konektokandidatojn (ICE-eraro 2021)", + "app.sfu.serverIceGatheringFailed2022": "Konekto kun la aÅdvida servilo malsukcesis (ICE-eraro 2022)", + "app.sfu.mediaGenericError2200": "AÅdvida servilo malsukcesis trakti la peton (eraro 2200)", + "app.sfu.invalidSdp2202":"Kliento generis nevalidan aÅdvidan peton (SDP-eraro 2202)", + "app.sfu.noAvailableCodec2203": "Servilo ne povis trovi taÅgan kodekon (eraro 2203)", + "app.meeting.endNotification.ok.label": "Bone", + "app.whiteboard.toolbar.tools": "Iloj", + "app.whiteboard.toolbar.tools.hand": "Panoramilo", + "app.whiteboard.toolbar.tools.pencil": "Krajono", + "app.whiteboard.toolbar.tools.rectangle": "Ortangulo", + "app.whiteboard.toolbar.tools.triangle": "Triangulo", + "app.whiteboard.toolbar.tools.ellipse": "Elipso", + "app.whiteboard.toolbar.tools.line": "Linio", + "app.whiteboard.toolbar.tools.text": "Teksto", + "app.whiteboard.toolbar.thickness": "Desegna diko", + "app.whiteboard.toolbar.thicknessDisabled": "Desegna diko estas malebligita", + "app.whiteboard.toolbar.color": "Koloroj", + "app.whiteboard.toolbar.colorDisabled": "Koloroj estas malebligitaj", + "app.whiteboard.toolbar.color.black": "Nigra", + "app.whiteboard.toolbar.color.white": "Blanka", + "app.whiteboard.toolbar.color.red": "RuÄa", + "app.whiteboard.toolbar.color.orange": "OranÄa", + "app.whiteboard.toolbar.color.eletricLime": "Helflavverda", + "app.whiteboard.toolbar.color.lime": "Flavverda", + "app.whiteboard.toolbar.color.cyan": "Cejana", + "app.whiteboard.toolbar.color.dodgerBlue": "DoÄerblua", + "app.whiteboard.toolbar.color.blue": "Blua", + "app.whiteboard.toolbar.color.violet": "Viola", + "app.whiteboard.toolbar.color.magenta": "Fuksina", + "app.whiteboard.toolbar.color.silver": "ArÄenta", + "app.whiteboard.toolbar.undo": "Malfari prinoton", + "app.whiteboard.toolbar.clear": "ViÅi ĉiujn prinotojn", + "app.whiteboard.toolbar.multiUserOn": "Åœalti pluruzantecon de blanktabulo", + "app.whiteboard.toolbar.multiUserOff": "MalÅalti pluruzantecon de blanktabulo", + "app.whiteboard.toolbar.fontSize": "Listo de tipargrandoj", + "app.feedback.title": "Vi elsalutis el la kunsido", + "app.feedback.subtitle": "Ni Åategus aÅdi pri via sperto kun BigBlueButton (nedeviga)", + "app.feedback.textarea": "Kiel ni povus plibonigi BigBlueButton?", + "app.feedback.sendFeedback": "Sendi Pritakson", + "app.feedback.sendFeedbackDesc": "Sendas pritakson kaj forlasas la kunsidon", + "app.videoDock.webcamFocusLabel": "Enfokusigi", + "app.videoDock.webcamFocusDesc": "Enfokusigas la elektitan kameraon", + "app.videoDock.webcamUnfocusLabel": "Elfokusigi", + "app.videoDock.webcamUnfocusDesc": "Elfokusigas la elektitan kameraon", + "app.videoDock.autoplayBlockedDesc": "Ni bezonas vian permeson montri al vi la kameraojn de aliaj uzantoj.", + "app.videoDock.autoplayAllowLabel": "Rigardi kameraojn", + "app.invitation.title": "Invito al apartigita ĉambro", + "app.invitation.confirm": "Inviti", + "app.createBreakoutRoom.title": "Apartigitaj Ĉambroj", + "app.createBreakoutRoom.ariaTitle": "KaÅi Apartigitajn Ĉambrojn", + "app.createBreakoutRoom.breakoutRoomLabel": "Apartigitaj Ĉambroj {0}", + "app.createBreakoutRoom.generatingURL": "Generante URL", + "app.createBreakoutRoom.generatedURL": "Generite", + "app.createBreakoutRoom.duration": "DaÅro {0}", + "app.createBreakoutRoom.room": "Ĉambro {0}", + "app.createBreakoutRoom.notAssigned": "Neasignita(j) ({0})", + "app.createBreakoutRoom.join": "Eniri ĉambron", + "app.createBreakoutRoom.joinAudio": "KonektiÄi kun sono", + "app.createBreakoutRoom.returnAudio": "Redoni sonon", + "app.createBreakoutRoom.alreadyConnected": "Jam en ĉambro", + "app.createBreakoutRoom.confirm": "Krei", + "app.createBreakoutRoom.record": "Registri", + "app.createBreakoutRoom.numberOfRooms": "Kvanto de ĉambroj", + "app.createBreakoutRoom.durationInMinutes": "DaÅro (minutoj)", + "app.createBreakoutRoom.randomlyAssign": "Arbitre asigni", + "app.createBreakoutRoom.endAllBreakouts": "Fini ĉiujn apartigitajn ĉambrojn", + "app.createBreakoutRoom.roomName": "{0} (Ĉambro - {1})", + "app.createBreakoutRoom.doneLabel": "Preta", + "app.createBreakoutRoom.nextLabel": "Sekva", + "app.createBreakoutRoom.minusRoomTime": "Malpliigi tempon de apartigita ĉambro al", + "app.createBreakoutRoom.addRoomTime": "Pliigi tempon de apartigita ĉambro al", + "app.createBreakoutRoom.addParticipantLabel": "+ Aldoni partoprenanton", + "app.createBreakoutRoom.freeJoin": "Permesi al ĉiuj uzantoj elekti apartigitan ĉambron por eniri", + "app.createBreakoutRoom.leastOneWarnBreakout": "Vi devas meti almenaÅ unu uzanton en apartigitan ĉambron.", + "app.createBreakoutRoom.modalDesc": "Helpeto: Vi povas Åovi kaj demeti uzantnomon por asigni rin al specifa apartigita ĉambro.", + "app.createBreakoutRoom.roomTime": "{0} minuto(j)", + "app.createBreakoutRoom.numberOfRoomsError": "La kvanto de ĉambroj estas nevalida.", + "app.externalVideo.start": "Kundividi novan videon", + "app.externalVideo.title": "Kundividi eksteran videon", + "app.externalVideo.input": "URL de la ekstera video", + "app.externalVideo.urlInput": "Aldoni URL de video", + "app.externalVideo.urlError": "Ĉi tiu videa URL ne estas subtenata", + "app.externalVideo.close": "Fermi", + "app.externalVideo.autoPlayWarning": "Ludigi la videon por ebligi aÅdvidan sinkronigon", + "app.network.connection.effective.slow": "Ni rimarkas konektajn problemojn.", + "app.network.connection.effective.slow.help": "Pli da informoj", + "app.externalVideo.noteLabel": "Rimarko: Eksteraj videoj ne aperos en la registraĵo. YouTube, Vimeo, Instructure Media, Twitch, Dailymotion kaj URL-oj de aÅdvidaj dosieroj (ekz. https://example.com/xy.mp4) estas subtenataj.", + "app.actionsBar.actionsDropdown.shareExternalVideo": "Kundividi eksteran videon", + "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Ĉesi kundividi eksteran videon", + "app.iOSWarning.label": "Bonvolu Äisdatigi al almenaÅ iOS 12.2", + "app.legacy.unsupportedBrowser": "Åœajnas, ke vi uzas retumilon, kiu ne estas subtenata. Bonvolu uzi aÅ {0} aÅ {1} por plena subteno.", + "app.legacy.upgradeBrowser": "Åœajnas, ke vi uzas malnovan version de subtenata reumilo. Bonvolu Äisdatigi vian retumilon por plena subteno.", + "app.legacy.criosBrowser": "Sur iOS bonvolu uzi Safari por plena subteno." + +} + diff --git a/bigbluebutton-html5/private/locales/es.json b/bigbluebutton-html5/private/locales/es.json index 724007d919f998cabcae9f737ecde754c8b7f3f3..9facb033c52f997df2604eb71f312d29ab5cb02b 100644 --- a/bigbluebutton-html5/private/locales/es.json +++ b/bigbluebutton-html5/private/locales/es.json @@ -1,5 +1,5 @@ { - "app.home.greeting": "Tu presentación se iniciará pronto...", + "app.home.greeting": "Su presentación se iniciará pronto...", "app.chat.submitLabel": "Enviar mensaje", "app.chat.errorMaxMessageLength": "El mensaje es {0} carácter(es) más largo de lo esperado", "app.chat.disconnected": "Estás desconectado, los mensajes no pueden ser enviados", @@ -18,6 +18,7 @@ "app.chat.dropdown.save": "Guardar", "app.chat.label": "Chat", "app.chat.offline": "Desconectado", + "app.chat.pollResult": "Resultados de la encuesta", "app.chat.emptyLogLabel": "Registro de chat vacÃo", "app.chat.clearPublicChatMessage": "El chat publico fue borrado por un moderador", "app.chat.multi.typing": "Varios usuarios están escribiendo", @@ -50,10 +51,10 @@ "app.note.title": "Notas compartidas", "app.note.label": "Nota", "app.note.hideNoteLabel": "Ocultar nota", + "app.note.tipLabel": "Presione Esc para enfocar la barra de herramientas del editor", "app.user.activityCheck": "Comprobar actividad del usuario", "app.user.activityCheck.label": "Comprobar si el usuario continúa en la reunión ({0})", "app.user.activityCheck.check": "Comprobar", - "app.note.tipLabel": "Presione Esc para enfocar la barra de herramientas del editor", "app.userList.usersTitle": "Usuarios", "app.userList.participantsTitle": "Participantes", "app.userList.messagesTitle": "Mensajes", @@ -61,12 +62,12 @@ "app.userList.notesListItem.unreadContent": "Contenido nuevo disponible en la sección de notas compartidas", "app.userList.captionsTitle": "SubtÃtulos", "app.userList.presenter": "Presentador", - "app.userList.you": "Tu", + "app.userList.you": "Tú", "app.userList.locked": "Bloqueado", "app.userList.byModerator": "por (Moderador)", "app.userList.label": "Lista de usuarios", "app.userList.toggleCompactView.label": "Cambiar a modo de vista compacta", - "app.userList.guest": "Huesped", + "app.userList.guest": "Invitado", "app.userList.menuTitleContext": "Opciones disponibles", "app.userList.chatListItem.unreadSingular": "{0} Nuevo Mensaje", "app.userList.chatListItem.unreadPlural": "{0} Nuevos mensajes", @@ -95,6 +96,8 @@ "app.userList.userOptions.unmuteAllDesc": "Habilitar audio en la sesión", "app.userList.userOptions.lockViewersLabel": "Bloquear espectadores", "app.userList.userOptions.lockViewersDesc": "Bloquear algunas funciones a espectadores", + "app.userList.userOptions.connectionStatusLabel": "Estado de la conexión", + "app.userList.userOptions.connectionStatusDesc": "Ver el estado de la conexión de los usuarios", "app.userList.userOptions.disableCam": "Cámaras web de invitados deshabilitadas", "app.userList.userOptions.disableMic": "Micrófonos de invitados deshabilitados", "app.userList.userOptions.disablePrivChat": "Chat privado deshabilitado", @@ -126,8 +129,10 @@ "app.meeting.meetingTimeRemaining": "Tiempo restante de la reunión: {0}", "app.meeting.meetingTimeHasEnded": "Tiempo finalizado. La reunión se cerrará en breve", "app.meeting.endedMessage": "Serás enviado a la pantalla de inicio.", - "app.meeting.alertMeetingEndsUnderOneMinute": "La reunión se cierra en un minuto", - "app.meeting.alertBreakoutEndsUnderOneMinute": "La micro-sala se cierra en un minuto", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "La reunión se cerrará en un minuto.", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "La reunión se cerrará en {0} minutos.", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "Las salas secundarias se cerrarán en {0} minutos.", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "Las salas secundarias se cerrarán en un minuto.", "app.presentation.hide": "Ocultar presentación", "app.presentation.notificationLabel": "Presentación actual", "app.presentation.slideContent": "Contenido de la diapositiva", @@ -267,7 +272,7 @@ "app.leaveConfirmation.confirmLabel": "Salir", "app.leaveConfirmation.confirmDesc": "Te desconecta de la reunión", "app.endMeeting.title": "Finalizar sesión", - "app.endMeeting.description": "¿Estás seguro de querer finalizar la sesión?", + "app.endMeeting.description": "¿Está seguro de querer finalizar esta reunión para todos los participantes (todos los usuarios serán desconectados)?", "app.endMeeting.yesLabel": "SÃ", "app.endMeeting.noLabel": "No", "app.about.title": "Acerca de", @@ -288,10 +293,6 @@ "app.screenshare.screenShareLabel" : "Compartir pantalla", "app.submenu.application.applicationSectionTitle": "Aplicación", "app.submenu.application.animationsLabel": "Animaciones", - "app.submenu.application.audioAlertLabel": "Alertas de sonido para el chat", - "app.submenu.application.pushAlertLabel": "Alertas visuales para el chat", - "app.submenu.application.userJoinAudioAlertLabel": "Alertas de audio para usuario entrante", - "app.submenu.application.userJoinPushAlertLabel": "Alertas emergentes para usuario entrante", "app.submenu.application.fontSizeControlLabel": "Tamaño de fuente", "app.submenu.application.increaseFontBtnLabel": "Incrementar tamaño de fuente", "app.submenu.application.decreaseFontBtnLabel": "Reducir tamaño de fuente", @@ -299,6 +300,12 @@ "app.submenu.application.languageLabel": "Lenguaje de aplicación", "app.submenu.application.languageOptionLabel": "Seleccionar lenguaje", "app.submenu.application.noLocaleOptionLabel": "No hay locales activos", + "app.submenu.notification.SectionTitle": "Notificaciones", + "app.submenu.notification.Desc": "Defina cómo y de qué será notificado.", + "app.submenu.notification.audioAlertLabel": "Alertas audibles", + "app.submenu.notification.pushAlertLabel": "Alertas visuales", + "app.submenu.notification.messagesLabel": "Mensaje de chat", + "app.submenu.notification.userJoinLabel": "Usuario se une", "app.submenu.audio.micSourceLabel": "Fuente de micrófono", "app.submenu.audio.speakerSourceLabel": "Fuente de altavoces", "app.submenu.audio.streamVolumeLabel": "Volumen del flujo de audio", @@ -385,7 +392,7 @@ "app.audioNotification.audioFailedMessage": "Tu conexión de audio falló en conectarse", "app.audioNotification.mediaFailedMessage": "getUserMicMedia falló, Solo orÃgenes seguros son admitidos", "app.audioNotification.closeLabel": "Cerrar", - "app.audioNotificaion.reconnectingAsListenOnly": "El micrófono ha sido bloqueado para todos los espectadores, tu conexión es en modo \"solo escuchar\"", + "app.audioNotificaion.reconnectingAsListenOnly": "El micrófono ha sido bloqueado para todos los espectadores, tu conexión es en modo 'solo escuchar'", "app.breakoutJoinConfirmation.title": "Ingresar a un grupo de trabajo", "app.breakoutJoinConfirmation.message": "¿Deseas entrar a la ", "app.breakoutJoinConfirmation.confirmDesc": "Ingresar a un grupo de trabajo", @@ -486,6 +493,8 @@ "app.userList.guest.pendingGuestUsers": "{0} Invitados pendientes", "app.userList.guest.pendingGuestAlert": "Se ha unido a la sesión y está esperando su aprobación.", "app.userList.guest.rememberChoice": "Recordar elección", + "app.userList.guest.acceptLabel": "Aceptar", + "app.userList.guest.denyLabel": "Denegar", "app.user-info.title": "Búsqueda de directorio", "app.toast.breakoutRoomEnded": "La sesión de grupo de trabajo ha finalizado. Ingresa al audio nuevamente.", "app.toast.chat.public": "Nuevo mensaje en chat público", @@ -500,6 +509,7 @@ "app.notification.recordingPaused": "Se ha dejado de grabar la sesión", "app.notification.recordingAriaLabel": "Tiempo de grabación", "app.notification.userJoinPushAlert": "{0} se ha unido a la sesión", + "app.submenu.notification.raiseHandLabel": "Levantar la mano", "app.shortcut-help.title": "Atajos de teclado", "app.shortcut-help.accessKeyNotAvailable": "Teclas de acceso no disponibles", "app.shortcut-help.comboLabel": "Combinación", @@ -533,6 +543,12 @@ "app.lock-viewers.button.cancel": "Cancelar", "app.lock-viewers.locked": "Bloqueado", "app.lock-viewers.unlocked": "Desbloqueado", + "app.connection-status.ariaTitle": "Estado de conexión modal", + "app.connection-status.title": "Estado de la conexión", + "app.connection-status.description": "Ver el estado de conexión de los usuarios", + "app.connection-status.empty": "No existe ningún problema de conectividad comunicado hasta ahora", + "app.connection-status.more": "Más", + "app.connection-status.offline": "Fuera de lÃnea", "app.recording.startTitle": "Iniciar grabación", "app.recording.stopTitle": "Pausar grabación", "app.recording.resumeTitle": "Continuar grabación", @@ -544,6 +560,8 @@ "app.videoPreview.closeLabel": "Cerrar", "app.videoPreview.findingWebcamsLabel": "Buscando webcams", "app.videoPreview.startSharingLabel": "Compartir cámara", + "app.videoPreview.stopSharingLabel": "Detener compartir", + "app.videoPreview.sharedCameraLabel": "Esta cámara ya está siendo compartida", "app.videoPreview.webcamOptionLabel": "Selecciona la webcam", "app.videoPreview.webcamPreviewLabel": "Vista preliminar de webcam", "app.videoPreview.webcamSettingsTitle": "Configuración de webcam", @@ -573,19 +591,6 @@ "app.video.videoMenuDesc": "Abrir el menú de video", "app.video.chromeExtensionError": "Debes instalar", "app.video.chromeExtensionErrorLink": "esta extensión de Chrome", - "app.video.stats.title": "EstadÃsticas de conexión", - "app.video.stats.packetsReceived": "Paquetes recibidos", - "app.video.stats.packetsSent": "Paquetes enviados", - "app.video.stats.packetsLost": "Paquetes perdidos", - "app.video.stats.bitrate": "Bitrate", - "app.video.stats.lostPercentage": "Porcentaje total de perdida", - "app.video.stats.lostRecentPercentage": "Porcentaje de pérdida reciente", - "app.video.stats.dimensions": "Dimensiones", - "app.video.stats.codec": "Códec", - "app.video.stats.decodeDelay": "Demora de decodificación", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Uso de codificador", - "app.video.stats.currentDelay": "Demora actual", "app.fullscreenButton.label": "Hacer {0} pantalla completa", "app.deskshare.iceConnectionStateError": "Falló la conexión al compartir pantalla (ICE error 1108)", "app.sfu.mediaServerConnectionError2000": "No se pudo conectar al servidor de medios (error 2000)", @@ -598,7 +603,6 @@ "app.sfu.invalidSdp2202":"El cliente generó una petición de medios inválida (SDP error 2202)", "app.sfu.noAvailableCodec2203": "El servidor no pudo encontrar un códec apropiado (error 2203)", "app.meeting.endNotification.ok.label": "OK", - "app.whiteboard.annotations.poll": "Resultados de la encuesta compartidos", "app.whiteboard.toolbar.tools": "Herramientas", "app.whiteboard.toolbar.tools.hand": "Panorama", "app.whiteboard.toolbar.tools.pencil": "Lápiz", @@ -679,7 +683,7 @@ "app.externalVideo.autoPlayWarning": "Reproduzca el video para activar la sincronización de medios", "app.network.connection.effective.slow": "Estamos detectando problemas de conectividad.", "app.network.connection.effective.slow.help": "Más información", - "app.externalVideo.noteLabel": "Nota: Los videos externos compartidos no aparecerán en la grabación. Se admiten URLs de YouTube, Vimeo, Instructure Media, Twitch y Daily Motion.", + "app.externalVideo.noteLabel": "Nota: Los vÃdeos externos compartidos no aparecerán en la grabación. YouTube, Vimeo, Instructure Media, Twitch, Dailymotion y direcciones de archivos multimedia (p.e. URLs como https://example.com/xy.mp4) están soportados.", "app.actionsBar.actionsDropdown.shareExternalVideo": "Compartir un video externo", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Dejar de compartir video externo", "app.iOSWarning.label": "Por favor, actualice a iOS 12.2 o superior", diff --git a/bigbluebutton-html5/private/locales/es_ES.json b/bigbluebutton-html5/private/locales/es_ES.json index eab4156fe0706a3faddefbd3675748d2922b3e47..8a0e4179c8989ac6a282cbba8c7c3655485b8c27 100644 --- a/bigbluebutton-html5/private/locales/es_ES.json +++ b/bigbluebutton-html5/private/locales/es_ES.json @@ -50,10 +50,10 @@ "app.note.title": "Notas compartidas", "app.note.label": "Nota", "app.note.hideNoteLabel": "Nota oculta", + "app.note.tipLabel": "Presionar 'Esc' para situarse en la barra de herramientas del editor", "app.user.activityCheck": "Comprobación de la actividad del usuario", "app.user.activityCheck.label": "Comprobar si el usuario continúa en la reunión ({0})", "app.user.activityCheck.check": "Comprobar", - "app.note.tipLabel": "Presionar 'Esc' para situarse en la barra de herramientas del editor", "app.userList.usersTitle": "Usuarios", "app.userList.participantsTitle": "Participantes", "app.userList.messagesTitle": "Mensajes", @@ -121,8 +121,6 @@ "app.meeting.meetingTimeRemaining": "Tiempo restante de la reunión: {0}", "app.meeting.meetingTimeHasEnded": "Tiempo finalizado. La reunión se cerrará pronto", "app.meeting.endedMessage": "Será reenviado a la pantalla de inicio", - "app.meeting.alertMeetingEndsUnderOneMinute": "La reunión finaliza en un minuto.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "La Sala externa se cerrará en un minuto", "app.presentation.hide": "Ocultar presentación", "app.presentation.notificationLabel": "Presentación actual", "app.presentation.slideContent": "Contenido de diapositiva", @@ -256,7 +254,6 @@ "app.leaveConfirmation.confirmLabel": "Salir", "app.leaveConfirmation.confirmDesc": "Le desconecta de la reunión", "app.endMeeting.title": "Finalizar reunión", - "app.endMeeting.description": "¿Está seguro de querer finalizar la sesión?", "app.endMeeting.yesLabel": "SÃ", "app.endMeeting.noLabel": "No", "app.about.title": "Acerca de", @@ -277,10 +274,6 @@ "app.screenshare.screenShareLabel" : "Compartir pantalla", "app.submenu.application.applicationSectionTitle": "Aplicación", "app.submenu.application.animationsLabel": "Animaciones", - "app.submenu.application.audioAlertLabel": "Alertas sonoras para el chat", - "app.submenu.application.pushAlertLabel": "Alertas visuales para el chat", - "app.submenu.application.userJoinAudioAlertLabel": "Alertas sonoras por usuarios que se unen", - "app.submenu.application.userJoinPushAlertLabel": "Alertas visuales por usuarios que se unen", "app.submenu.application.fontSizeControlLabel": "Tamaño de fuente", "app.submenu.application.increaseFontBtnLabel": "Aumentar tamaño de fuente de la aplicación", "app.submenu.application.decreaseFontBtnLabel": "Reducir tamaño de fuente de la aplicación", @@ -548,22 +541,8 @@ "app.video.videoMenuDesc": "Abrir menú desplegable de video", "app.video.chromeExtensionError": "Usted debe instalar", "app.video.chromeExtensionErrorLink": "esta extensión de Chrome", - "app.video.stats.title": "Estado de la conexión", - "app.video.stats.packetsReceived": "Paquetes recibidos", - "app.video.stats.packetsSent": "Paquetes enviados", - "app.video.stats.packetsLost": "Paquetes perdidos", - "app.video.stats.bitrate": "Bitrate", - "app.video.stats.lostPercentage": "Porcentaje total perdido", - "app.video.stats.lostRecentPercentage": "Porcentaje reciente perdido", - "app.video.stats.dimensions": "Dimensiones", - "app.video.stats.codec": "Codec", - "app.video.stats.decodeDelay": "Retardo de decodificación", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Codificar uso", - "app.video.stats.currentDelay": "Retardo actual", "app.fullscreenButton.label": "Hacer {0} pantalla completa", "app.meeting.endNotification.ok.label": "OK", - "app.whiteboard.annotations.poll": "Los resultados de la encuesta fueron publicados", "app.whiteboard.toolbar.tools": "Herramientas", "app.whiteboard.toolbar.tools.hand": "Expandir", "app.whiteboard.toolbar.tools.pencil": "Lápiz", @@ -644,13 +623,12 @@ "app.externalVideo.autoPlayWarning": "Reproduzca el video para habilitar la sincronización de medios", "app.network.connection.effective.slow": "Estamos notando problemas de conectividad.", "app.network.connection.effective.slow.help": "Más información", - "app.externalVideo.noteLabel": "Nota: los videos externos compartidos no aparecerán en la grabación. Se admiten las URL de YouTube, Vimeo, Instructure Media, Twitch y Daily Motion.", "app.actionsBar.actionsDropdown.shareExternalVideo": "Compartir un video externo", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Detener compartir video externo", "app.iOSWarning.label": "Por favor, actualice a iOS 12.2 o mayor", "app.legacy.unsupportedBrowser": "Parece que estás usando un navegador que no es compatible. Utilice {0} o {1} para obtener soporte completo.", "app.legacy.upgradeBrowser": "Parece que está usando una versión antigua no soportada del navegador. Por favor, actualice su navegador para soporte total.", - "app.legacy.criosBrowser": "En iOS, use Safari para obtener soporte completo." + "app.legacy.criosBrowser": "En iOS use Safari para obtener soporte completo." } diff --git a/bigbluebutton-html5/private/locales/es_MX.json b/bigbluebutton-html5/private/locales/es_MX.json index 4a3244f9d03f026c0277a9ca3a8848a8f6ff65c6..ea404c69935c1dd2ca0732a30135f2f0ce51993d 100644 --- a/bigbluebutton-html5/private/locales/es_MX.json +++ b/bigbluebutton-html5/private/locales/es_MX.json @@ -25,10 +25,10 @@ "app.note.title": "Notas compartidas", "app.note.label": "Nota", "app.note.hideNoteLabel": "Ocultar nota", + "app.note.tipLabel": "Pulse la tecla Esc para enfocar la barra de herramientas de edición", "app.user.activityCheck": "Verificar actividad del usuario", "app.user.activityCheck.label": "Verificar que el usuario se encuentre en la sesión ({0})", "app.user.activityCheck.check": "Verificar", - "app.note.tipLabel": "Pulse la tecla Esc para enfocar la barra de herramientas de edición", "app.userList.usersTitle": "Usuarios", "app.userList.participantsTitle": "Participantes", "app.userList.messagesTitle": "Mensajes", @@ -75,8 +75,6 @@ "app.meeting.meetingTimeRemaining": "Tiempo restante en la sesión: {0}", "app.meeting.meetingTimeHasEnded": "El tiempo a finalizado. La sesión se cerrara en cualquier momento", "app.meeting.endedMessage": "Serás enviado a la pantalla de inicio.", - "app.meeting.alertMeetingEndsUnderOneMinute": "La sesión se cerrara en un minuto.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "El grupo de trabajo se cerrara en un minuto", "app.presentation.hide": "Ocultar la presentación", "app.presentation.slideContent": "Contenido de diapositiva", "app.presentation.startSlideContent": "Inicio de la presentación", @@ -186,7 +184,6 @@ "app.leaveConfirmation.confirmLabel": "Salir", "app.leaveConfirmation.confirmDesc": "Te desconecta de la reunión", "app.endMeeting.title": "Finalizar sesión", - "app.endMeeting.description": "¿Estas seguro de querer finalizar la sesión?", "app.endMeeting.yesLabel": "Si", "app.endMeeting.noLabel": "No", "app.about.title": "Acerca de", @@ -207,8 +204,6 @@ "app.screenshare.screenShareLabel" : "Compartir pantalla", "app.submenu.application.applicationSectionTitle": "Aplicación", "app.submenu.application.animationsLabel": "Animaciones", - "app.submenu.application.audioAlertLabel": "Alertas de sonido para chat", - "app.submenu.application.pushAlertLabel": "Alertas emergentes para chat", "app.submenu.application.fontSizeControlLabel": "Tamaño de fuente", "app.submenu.application.increaseFontBtnLabel": "Incrementar tamaño de fuente", "app.submenu.application.decreaseFontBtnLabel": "Reducir tamaño de fuente", @@ -279,7 +274,7 @@ "app.audioNotification.audioFailedMessage": "Tu conexión de audio falló en conectarse", "app.audioNotification.mediaFailedMessage": "getUserMicMedia falló, Solo orÃgenes seguros son admitidos", "app.audioNotification.closeLabel": "Cerrar", - "app.audioNotificaion.reconnectingAsListenOnly": "El micrófono ha sido bloqueado para todos los espectadores, tu conexión es en modo \"solo escuchar\"", + "app.audioNotificaion.reconnectingAsListenOnly": "El micrófono ha sido bloqueado para todos los espectadores, tu conexión es en modo 'solo escuchar'", "app.breakoutJoinConfirmation.title": "Ingresar a un grupo de trabajo", "app.breakoutJoinConfirmation.message": "Quieres unirte", "app.breakoutJoinConfirmation.confirmDesc": "Ingresar a un grupo de trabajo", @@ -418,19 +413,6 @@ "app.video.videoMenuDesc": "Abrir el menú de video", "app.video.chromeExtensionError": "Debes instalar", "app.video.chromeExtensionErrorLink": "esta extensión de Chrome", - "app.video.stats.title": "EstadÃsticas de conexión", - "app.video.stats.packetsReceived": "Paquetes recibidos", - "app.video.stats.packetsSent": "Paquetes enviados", - "app.video.stats.packetsLost": "Paquetes perdidos", - "app.video.stats.bitrate": "Bitrate", - "app.video.stats.lostPercentage": "Porcentaje total de perdida", - "app.video.stats.lostRecentPercentage": "Porcentaje de pérdida reciente", - "app.video.stats.dimensions": "Dimensiones", - "app.video.stats.codec": "Codec", - "app.video.stats.decodeDelay": "Demora de decodificación", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Uso de codificador", - "app.video.stats.currentDelay": "Demora actual", "app.fullscreenButton.label": "Visualizar {0} en pantalla completa", "app.meeting.endNotification.ok.label": "OK", "app.whiteboard.toolbar.tools": "Herramientas", diff --git a/bigbluebutton-html5/private/locales/et.json b/bigbluebutton-html5/private/locales/et.json index 6ca8ecc11f2e66c6f0b85bae0c6d4fbc20b28777..a54bde1ef772214dda940d9406eb115a30b53d37 100644 --- a/bigbluebutton-html5/private/locales/et.json +++ b/bigbluebutton-html5/private/locales/et.json @@ -3,23 +3,24 @@ "app.chat.submitLabel": "Saada sõnum", "app.chat.errorMaxMessageLength": "Sõnum on {0} tähemärk(i) liiga pikk", "app.chat.disconnected": "Ühendus on katkenud, sõnumeid ei saa saata", - "app.chat.locked": "Sõnumivahetus on lukus, sõnumeid ei saa saata", - "app.chat.inputLabel": "Sõnum sõnumivahetuse {0} jaoks", + "app.chat.locked": "Vestlus on lukus, sõnumeid ei saa saata", + "app.chat.inputLabel": "Sõnum vestluse {0} jaoks", "app.chat.inputPlaceholder": "Saada sõnum kasutajale {0}", - "app.chat.titlePublic": "Avalik sõnumivahetus", - "app.chat.titlePrivate": "Privaatne sõnumivahetus kasutajaga {0}", + "app.chat.titlePublic": "Avalik vestlus", + "app.chat.titlePrivate": "Privaatne vestlus kasutajaga {0}", "app.chat.partnerDisconnected": "{0} lahkus ruumist", "app.chat.closeChatLabel": "Sulge {0}", "app.chat.hideChatLabel": "Peida {0}", "app.chat.moreMessages": "Rohkem sõnumeid allpool", - "app.chat.dropdown.options": "Sõnumivahetuse sätted", + "app.chat.dropdown.options": "Vestluse sätted", "app.chat.dropdown.clear": "Puhasta", "app.chat.dropdown.copy": "Kopeeri", "app.chat.dropdown.save": "Salvesta", - "app.chat.label": "Sõnumivahetus", + "app.chat.label": "Vestlus", "app.chat.offline": "Väljas", - "app.chat.emptyLogLabel": "Sõnumivahetuse logi on tühi", - "app.chat.clearPublicChatMessage": "Avalik sõnumivahetuse ajalugu kustutati moderaatori poolt", + "app.chat.pollResult": "Küsitluse tulemused", + "app.chat.emptyLogLabel": "Vestluse logi on tühi", + "app.chat.clearPublicChatMessage": "Avaliku vestluse ajalugu kustutati moderaatori poolt", "app.chat.multi.typing": "Mitu kasutajat kirjutavad", "app.chat.one.typing": "{0} kirjutab", "app.chat.two.typing": "{0} ja {1} kirjutavad", @@ -50,10 +51,10 @@ "app.note.title": "Jagatud märkmed", "app.note.label": "Märge", "app.note.hideNoteLabel": "Peida märge", + "app.note.tipLabel": "Vajuta Esc-klahvi, et valida redaktori tööriistariba", "app.user.activityCheck": "Kasutaja tegevuse kontroll", "app.user.activityCheck.label": "Kontrolli kas kasutaja ({0}) on veel ruumis", "app.user.activityCheck.check": "Kontrolli", - "app.note.tipLabel": "Vajuta Esc-klahvi, et valida redaktori tööriistariba", "app.userList.usersTitle": "Kasutajad", "app.userList.participantsTitle": "Osalejad", "app.userList.messagesTitle": "Sõnumid", @@ -63,22 +64,23 @@ "app.userList.presenter": "Esitleja", "app.userList.you": "Sina", "app.userList.locked": "Lukustatud", + "app.userList.byModerator": "(moderaatori poolt)", "app.userList.label": "Kasutajate nimekiri", "app.userList.toggleCompactView.label": "Vaheta kompaktse vaate vahel", "app.userList.guest": "Külaline", "app.userList.menuTitleContext": "Saadaval olevad valikud", "app.userList.chatListItem.unreadSingular": "{0} uus sõnum", "app.userList.chatListItem.unreadPlural": "{0} uut sõnumit", - "app.userList.menu.chat.label": "Alusta privaatset sõnumivahetust", + "app.userList.menu.chat.label": "Alusta privaatset vestlust", "app.userList.menu.clearStatus.label": "Tühista staatus", "app.userList.menu.removeUser.label": "Eemalda kasutaja", "app.userList.menu.removeConfirmation.label": "Eemalda kasutaja ({0})", - "app.userlist.menu.removeConfirmation.desc": "Takista kasutaja sessiooniga taasliitumine.", + "app.userlist.menu.removeConfirmation.desc": "Takista kasutajal sessiooniga taasliituda.", "app.userList.menu.muteUserAudio.label": "Vaigista kasutaja", "app.userList.menu.unmuteUserAudio.label": "Eemalda kasutaja vaigistus", "app.userList.userAriaLabel": "{0} {1} {2} staatus {3}", - "app.userList.menu.promoteUser.label": "Muuda moderaatoriks", - "app.userList.menu.demoteUser.label": "Muuda vaatajaks", + "app.userList.menu.promoteUser.label": "Ülenda moderaatoriks", + "app.userList.menu.demoteUser.label": "Alanda vaatajaks", "app.userList.menu.unlockUser.label": "Ava {0}", "app.userList.menu.lockUser.label": "Lukusta {0}", "app.userList.menu.directoryLookup.label": "Otsi kataloogist", @@ -94,18 +96,20 @@ "app.userList.userOptions.unmuteAllDesc": "Taastab ruumi heli", "app.userList.userOptions.lockViewersLabel": "Lukusta vaatajad", "app.userList.userOptions.lockViewersDesc": "Lukusta ruumis osalejate teatud funktsionaalsused", + "app.userList.userOptions.connectionStatusLabel": "Ühenduse staatus", + "app.userList.userOptions.connectionStatusDesc": "Vaata kasutajate ühenduse staatust", "app.userList.userOptions.disableCam": "Vaatajate veebikaamerad on keelatud", "app.userList.userOptions.disableMic": "Vaatajate mikrofonid on keelatud", - "app.userList.userOptions.disablePrivChat": "Privaatne sõnumivahetus on keelatud", - "app.userList.userOptions.disablePubChat": "Avalik sõnumivahetus on keelatud", + "app.userList.userOptions.disablePrivChat": "Privaatne vestlus on keelatud", + "app.userList.userOptions.disablePubChat": "Avalik vestlus on keelatud", "app.userList.userOptions.disableNote": "Jagatud märkmed on nüüd lukustatud", "app.userList.userOptions.hideUserList": "Kasutajate nimekiri on vaatajate eest peidetud", - "app.userList.userOptions.webcamsOnlyForModerator": "Ainult moderaatorid näevad kasutajate veebikaameraid (määratud seadetes)", - "app.userList.content.participants.options.clearedStatus": "Tühista kõikide kasutajate staatused", + "app.userList.userOptions.webcamsOnlyForModerator": "Ainult moderaatorid näevad kasutajate veebikaameraid (lukustamisvalikute tõttu)", + "app.userList.content.participants.options.clearedStatus": "Kõikide kasutajate staatused tühistati", "app.userList.userOptions.enableCam": "Vaatajate veebikaamerad on lubatud", "app.userList.userOptions.enableMic": "Vaatajate mikrofonid on lubatud", - "app.userList.userOptions.enablePrivChat": "Privaatne sõnumivahetus on lubatud", - "app.userList.userOptions.enablePubChat": "Avalik sõnumivahetus on lubatud", + "app.userList.userOptions.enablePrivChat": "Privaatne vestlus on lubatud", + "app.userList.userOptions.enablePubChat": "Avalik vestlus on lubatud", "app.userList.userOptions.enableNote": "Jagatud märkmed on nüüd lubatud", "app.userList.userOptions.showUserList": "Kasutajate nimekiri on nüüd vaatajatele nähtav", "app.userList.userOptions.enableOnlyModeratorWebcam": "Saad nüüd veebikaamera lubada ning kõik näevad seda", @@ -118,15 +122,17 @@ "app.media.screenshare.autoplayBlockedDesc": "Vajame sinu luba, et näidata sulle esitleja ekraani.", "app.media.screenshare.autoplayAllowLabel": "Vaata jagatud ekraani", "app.screenshare.notAllowed": "Viga: Ekraanijagamiseks ei antud luba", - "app.screenshare.notSupportedError": "Viga: Ekraanijagamine on lubatud ainult turvalistelt (SSL) domeenidelt", - "app.screenshare.notReadableError": "Viga: Ekraanijagamise alustamisel tekkis viga", + "app.screenshare.notSupportedError": "Viga: Ekraanijagamine on lubatud ainult turvalistel (SSL) domeenidel", + "app.screenshare.notReadableError": "Viga: Ekraanipildi hankimise katsel tekkis viga", "app.screenshare.genericError": "Viga: Ekraanijagamisel tekkis viga, palun proovi uuesti", "app.meeting.ended": "Sessioon on lõppenud", "app.meeting.meetingTimeRemaining": "Järelejäänud aeg ruumis: {0}", "app.meeting.meetingTimeHasEnded": "Aeg sai läbi. Ruum suletakse kohe", "app.meeting.endedMessage": "Sind suunatakse tagasi avalehele", - "app.meeting.alertMeetingEndsUnderOneMinute": "Ruum suletakse ühe minuti pärast.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Eraldatud ruumid suletakse ühe minuti pärast.", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "Sessioon sulgub ühe minuti pärast.", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "Sessioon sulgub {0} minuti pärast.", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "Ruum sulgub {0} minuti pärast.", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "Ruum sulgub ühe minuti pärast.", "app.presentation.hide": "Peida esitlus", "app.presentation.notificationLabel": "Aktiivne esitlus", "app.presentation.slideContent": "Slaidi sisu", @@ -171,8 +177,11 @@ "app.presentationUploder.fileToUpload": "Ootab üleslaadimist...", "app.presentationUploder.currentBadge": "Aktiivne", "app.presentationUploder.rejectedError": "Valitud fail(id) lükati tagasi. Palun kontrolli failitüüpi.", - "app.presentationUploder.upload.progress": "Laadin üles ({0}%)", + "app.presentationUploder.upload.progress": "Üleslaadimine ({0}%)", "app.presentationUploder.upload.413": "Fail on liiga suur. Palun jaga fail väiksemateks tükkideks.", + "app.presentationUploder.upload.408": "Taotle üleslaadimistõendi aegumist.", + "app.presentationUploder.upload.404": "404: Kehtetu üleslaadimistõend.", + "app.presentationUploder.upload.401": "Esitluse üleslaadimistõendi taotlemine ebaõnnestus.", "app.presentationUploder.conversion.conversionProcessingSlides": "Töötlen lehte {0} / {1}", "app.presentationUploder.conversion.genericConversionStatus": "Teisendan faili...", "app.presentationUploder.conversion.generatingThumbnail": "Genereerin pisipilte...", @@ -202,11 +211,11 @@ "app.poll.publishLabel": "Avalda küsitluse tulemused", "app.poll.backLabel": "Tagasi küsitluse valikute juurde", "app.poll.closeLabel": "Sulge", - "app.poll.waitingLabel": "Ootame vastuseid ({0}/{1})", + "app.poll.waitingLabel": "Vastuste ootamine ({0}/{1})", "app.poll.ariaInputCount": "Kohandatud küsitluse vastusevariant {0} / {1}", "app.poll.customPlaceholder": "Lisa küsitluse vastusevariant", "app.poll.noPresentationSelected": "Esitlust ei ole valitud! Palun vali esitlus.", - "app.poll.clickHereToSelect": "Vajuta valimiseks siia", + "app.poll.clickHereToSelect": "Valimiseks klõpsa siin", "app.poll.t": "Õige", "app.poll.f": "Vale", "app.poll.tf": "Õige / Vale", @@ -230,11 +239,11 @@ "app.poll.liveResult.responsesTitle": "Vastus", "app.polling.pollingTitle": "Küsitluse valikud", "app.polling.pollAnswerLabel": "Küsitluse vastus {0}", - "app.polling.pollAnswerDesc": "Vali see variant, et {0} poolt hääletada", + "app.polling.pollAnswerDesc": "Vali see variant, et hääletada {0} poolt", "app.failedMessage": "Vabandame! Serveriga ühendumisel esineb tõrkeid.", "app.downloadPresentationButton.label": "Laadi alla esitluse originaal", "app.connectingMessage": "Ühendumine...", - "app.waitingMessage": "Ühendus katkes. Proovime taas ühenduda {0} sekundi pärast ...", + "app.waitingMessage": "Ühendus katkes. Uus ühendumiskatse {0} sekundi pärast ...", "app.retryNow": "Proovi uuesti kohe", "app.navBar.settingsDropdown.optionsLabel": "Valikud", "app.navBar.settingsDropdown.fullscreenLabel": "Esita täisekraanil", @@ -263,7 +272,7 @@ "app.leaveConfirmation.confirmLabel": "Lahku", "app.leaveConfirmation.confirmDesc": "Logib sind ruumist välja", "app.endMeeting.title": "Lõpeta sessioon", - "app.endMeeting.description": "Kas oled kindel, et soovid sessiooni lõpetada?", + "app.endMeeting.description": "Kas oled kindel, et soovid selle sessiooni kõigi jaoks lõpetada (kõigi kasutajate ühendused katkestatakse)?", "app.endMeeting.yesLabel": "Jah", "app.endMeeting.noLabel": "Ei", "app.about.title": "Meist", @@ -284,10 +293,6 @@ "app.screenshare.screenShareLabel" : "Ekraanijagamine", "app.submenu.application.applicationSectionTitle": "Rakendus", "app.submenu.application.animationsLabel": "Animatsioonid", - "app.submenu.application.audioAlertLabel": "Sõnumivahetuse helimärguanded", - "app.submenu.application.pushAlertLabel": "Sõnumivahetuse hüpikmärguanded", - "app.submenu.application.userJoinAudioAlertLabel": "Kasutaja liitumise helimärguanded", - "app.submenu.application.userJoinPushAlertLabel": "Kasutaja liitumise hüpikmärguanded", "app.submenu.application.fontSizeControlLabel": "Teksti suurus", "app.submenu.application.increaseFontBtnLabel": "Suurenda rakenduse teksti suurust", "app.submenu.application.decreaseFontBtnLabel": "Vähenda rakenduste teksti suurust", @@ -295,6 +300,12 @@ "app.submenu.application.languageLabel": "Rakenduse keel", "app.submenu.application.languageOptionLabel": "Vali keel", "app.submenu.application.noLocaleOptionLabel": "Aktiivseid tõlkeid ei ole", + "app.submenu.notification.SectionTitle": "Teavitused", + "app.submenu.notification.Desc": "Määra, kuidas ja mille kohta teavitusi antakse.", + "app.submenu.notification.audioAlertLabel": "Audioteatised", + "app.submenu.notification.pushAlertLabel": "Hüpikteatised", + "app.submenu.notification.messagesLabel": "Sõnum vestluses", + "app.submenu.notification.userJoinLabel": "Kasutaja liitumine", "app.submenu.audio.micSourceLabel": "Mikrofoni sisend", "app.submenu.audio.speakerSourceLabel": "Kõlarite sisend", "app.submenu.audio.streamVolumeLabel": "Audiovoo helitugevus", @@ -320,7 +331,7 @@ "app.settings.save-notification.label": "Seaded on salvestatud", "app.switch.onLabel": "SEES", "app.switch.offLabel": "VÄLJAS", - "app.talkingIndicator.ariaMuteDesc" : "Vali kasutaja, keda vaigistada", + "app.talkingIndicator.ariaMuteDesc" : "Vali kasutaja vaigistamiseks", "app.talkingIndicator.isTalking" : "{0} räägib", "app.talkingIndicator.wasTalking" : "{0} lõpetas rääkimise", "app.actionsBar.actionsDropdown.actionsLabel": "Tegevused", @@ -329,15 +340,15 @@ "app.actionsBar.actionsDropdown.desktopShareLabel": "Jaga ekraani", "app.actionsBar.actionsDropdown.lockedDesktopShareLabel": "Ekraanijagamine on lukustatud", "app.actionsBar.actionsDropdown.stopDesktopShareLabel": "Lõpeta ekraanijagamine", - "app.actionsBar.actionsDropdown.presentationDesc": "Laadi oma esitlus üles", + "app.actionsBar.actionsDropdown.presentationDesc": "Laadi esitlus üles", "app.actionsBar.actionsDropdown.initPollDesc": "Alusta küsitlust", - "app.actionsBar.actionsDropdown.desktopShareDesc": "Jaga oma ekraani teistega", - "app.actionsBar.actionsDropdown.stopDesktopShareDesc": "Lõpeta oma ekraani jagamine", + "app.actionsBar.actionsDropdown.desktopShareDesc": "Jaga ekraani teistega", + "app.actionsBar.actionsDropdown.stopDesktopShareDesc": "Lõpeta ekraanijagamine", "app.actionsBar.actionsDropdown.pollBtnLabel": "Alusta küsitlust", "app.actionsBar.actionsDropdown.pollBtnDesc": "Vaheta küsitluse vaadet", "app.actionsBar.actionsDropdown.saveUserNames": "Salvesta kasutajate nimed", "app.actionsBar.actionsDropdown.createBreakoutRoom": "Loo eraldatud ruumid", - "app.actionsBar.actionsDropdown.createBreakoutRoomDesc": "Jaga sessioonil osalejad laiali mitmesse ruumi", + "app.actionsBar.actionsDropdown.createBreakoutRoomDesc": "Jaga sessioonil osalejad ruumidesse laiali", "app.actionsBar.actionsDropdown.captionsLabel": "Tiitrite kirjutamine", "app.actionsBar.actionsDropdown.captionsDesc": "Tiitrite paneel sisse/välja", "app.actionsBar.actionsDropdown.takePresenter": "Võta esitleja roll", @@ -383,7 +394,7 @@ "app.audioNotification.closeLabel": "Sulge", "app.audioNotificaion.reconnectingAsListenOnly": "Vaatajate mikrofonid on lukustatud, sind ühendatakse ainult kuulajana", "app.breakoutJoinConfirmation.title": "Liitu eraldatud ruumiga", - "app.breakoutJoinConfirmation.message": "Kas soovid liituda", + "app.breakoutJoinConfirmation.message": "Kas soovid liituda?", "app.breakoutJoinConfirmation.confirmDesc": "Liitu eraldatud ruumiga", "app.breakoutJoinConfirmation.dismissLabel": "Tühista", "app.breakoutJoinConfirmation.dismissDesc": "Sulgeb ja keelab eraldatud ruumiga liitumise", @@ -403,15 +414,15 @@ "app.audioModal.closeLabel": "Sulge", "app.audioModal.yes": "Jah", "app.audioModal.no": "Ei", - "app.audioModal.yes.arialabel" : "Kaja on kuulda", - "app.audioModal.no.arialabel" : "Kaja ei ole kuulda", - "app.audioModal.echoTestTitle": "See on privaatne kajatest. Ütle mõned sõnad. Kas kuuled ennast rääkimas?", + "app.audioModal.yes.arialabel" : "Heli on kuulda", + "app.audioModal.no.arialabel" : "Heli ei ole kuulda", + "app.audioModal.echoTestTitle": "See on privaatne helitest. Ütle mõned sõnad. Kas kuuled ennast rääkimas?", "app.audioModal.settingsTitle": "Muuda audioseadeid", "app.audioModal.helpTitle": "Sinu meediaseadmetega tekkis probleem", "app.audioModal.helpText": "Kas sa andsid mikrofonile juurdepääsuks loa? Kui püüad audioga liituda, siis peaks ilmuma dialoogiaken, kus küsitakse luba meediaseadme kasutamiseks. Palun anna luba, et audiosessiooniga ühineda. Kui on midagi muud, siis püüa muuta veebilehitseja seadetes mikrofoniga seotud õigusi.", "app.audioModal.help.noSSL": "See leht pole turvaline. Mikrofoni lubamiseks peab leht olema turvalisel HTTPS-ühendusel. Võta palun ühendust serveri administraatoriga.", "app.audioModal.help.macNotAllowed": "Paistab, et Maci süsteemiseaded blokeerivad mikrofonile juurdepääsu. Ava System Preferences > Security & Privacy > Privacy > Microphone ning veendu, et veebilehitseja, mida kasutad, on märgitud.", - "app.audioModal.audioDialTitle": "Liitu telefoni kaudu", + "app.audioModal.audioDialTitle": "Liitu telefoni abil", "app.audioDial.audioDialDescription": "Vali", "app.audioDial.audioDialConfrenceText": "ja sisesta sessiooni PIN-number:", "app.audioModal.autoplayBlockedDesc": "Vajame audio mängimiseks sinu luba", @@ -420,9 +431,9 @@ "app.audioDial.tipIndicator": "Soovitus", "app.audioDial.tipMessage": "Vajuta oma telefonil klahvi '0', et end vaigistada või vaigistus eemaldada.", "app.audioModal.connecting": "Ühendumine", - "app.audioModal.connectingEchoTest": "Ühendumine kajatestiga", + "app.audioModal.connectingEchoTest": "Ühendumine helitestiga", "app.audioManager.joinedAudio": "Oled audiokonverentsiga liitunud", - "app.audioManager.joinedEcho": "Kajatest käivitus", + "app.audioManager.joinedEcho": "Helitest käivitus", "app.audioManager.leftAudio": "Oled audiokonverentsilt lahkunud", "app.audioManager.reconnectingAudio": "Üritame uuesti audioga ühenduda", "app.audioManager.genericError": "Viga: Esines viga, palun proovi uuesti", @@ -433,7 +444,7 @@ "app.audio.joinAudio": "Liitu audioga", "app.audio.leaveAudio": "Lahku audiost", "app.audio.enterSessionLabel": "Liitu sessiooniga", - "app.audio.playSoundLabel": "Mängi heli", + "app.audio.playSoundLabel": "Mängi testheli", "app.audio.backLabel": "Tagasi", "app.audio.audioSettings.titleLabel": "Vali oma audio seaded", "app.audio.audioSettings.descriptionLabel": "Pane tähele, et veebilehitsejas avaneb dialoogiaken, kus palutakse luba sinu mikrofoni jagamiseks.", @@ -445,8 +456,8 @@ "app.audio.listenOnly.closeLabel": "Sulge", "app.audio.permissionsOverlay.title": "Luba juurdepääs oma mikrofonile", "app.audio.permissionsOverlay.hint": "Vajame sinu meediaseadmete kasutamiseks luba, et saaksime sind audiokonverentsiga ühendada :)", - "app.error.removed": "Oled konverentsilt eemaldatud", - "app.error.meeting.ended": "Oled konverentsist välja logitud", + "app.error.removed": "Oled sessioonilt eemaldatud", + "app.error.meeting.ended": "Oled sessioonist välja logitud", "app.meeting.logout.duplicateUserEjectReason": "Olemasolev kasutaja üritab ruumiga liituda", "app.meeting.logout.permissionEjectReason": "Tagasi lükatud õiguste rikkumise pärast", "app.meeting.logout.ejectedFromMeeting": "Oled ruumist eemaldatud", @@ -482,6 +493,8 @@ "app.userList.guest.pendingGuestUsers": "{0} ootel olevat külalist", "app.userList.guest.pendingGuestAlert": "On liitunud sessiooniga ning ootab sinu nõusolekut.", "app.userList.guest.rememberChoice": "Jäta valik meelde", + "app.userList.guest.acceptLabel": "Luba", + "app.userList.guest.denyLabel": "Keela", "app.user-info.title": "Otsi kataloogist", "app.toast.breakoutRoomEnded": "Eraldatud ruum aegus. Palun ühendu uuesti audioga.", "app.toast.chat.public": "Uus avalik sõnum", @@ -494,8 +507,9 @@ "app.notification.recordingStart": "Sessiooni salvestatakse", "app.notification.recordingStop": "Sessiooni ei salvestata", "app.notification.recordingPaused": "Sessiooni enam ei salvestata", - "app.notification.recordingAriaLabel": "Salvestatud aeg", + "app.notification.recordingAriaLabel": "Salvestuse kestus", "app.notification.userJoinPushAlert": "{0} liitus sessiooniga", + "app.submenu.notification.raiseHandLabel": "Tõsta käsi", "app.shortcut-help.title": "Kiirklahvid", "app.shortcut-help.accessKeyNotAvailable": "Juurdepääsuklahvid pole saadaval", "app.shortcut-help.comboLabel": "Kombo", @@ -506,8 +520,8 @@ "app.shortcut-help.toggleUserList": "Ava/peida kasutajate nimekiri", "app.shortcut-help.toggleMute": "Vaigista / Eemalda vaigistus", "app.shortcut-help.togglePublicChat": "Ava/peida avalik sõnumivahetus (kasutajate nimekiri peab olema avatud)", - "app.shortcut-help.hidePrivateChat": "Peida privaatne sõnumivahetus", - "app.shortcut-help.closePrivateChat": "Sulge privaatne sõnumivahetus", + "app.shortcut-help.hidePrivateChat": "Peida privaatne vestlus", + "app.shortcut-help.closePrivateChat": "Sulge privaatne vestlus", "app.shortcut-help.openActions": "Ava tegevustemenüü", "app.shortcut-help.openStatus": "Ava staatusemenüü", "app.shortcut-help.togglePan": "Aktiveeri liigutamistööriist (Esitleja)", @@ -529,6 +543,12 @@ "app.lock-viewers.button.cancel": "Tühista", "app.lock-viewers.locked": "Lukustatud", "app.lock-viewers.unlocked": "Avatud", + "app.connection-status.ariaTitle": "Ühenduse staatuse aken", + "app.connection-status.title": "Ühenduse staatus", + "app.connection-status.description": "Vaata kasutajate ühenduse staatust", + "app.connection-status.empty": "Ühestki ühendusprobleemist pole seni teatatud", + "app.connection-status.more": "veel", + "app.connection-status.offline": "väljas", "app.recording.startTitle": "Alusta salvestamist", "app.recording.stopTitle": "Peata salvestamine", "app.recording.resumeTitle": "Jätka salvestamist", @@ -540,6 +560,8 @@ "app.videoPreview.closeLabel": "Sulge", "app.videoPreview.findingWebcamsLabel": "Otsime veebikaameraid", "app.videoPreview.startSharingLabel": "Alusta jagamist", + "app.videoPreview.stopSharingLabel": "Lõpeta jagamine", + "app.videoPreview.sharedCameraLabel": "Seda kaamerat juba jagatakse", "app.videoPreview.webcamOptionLabel": "Vali veebikaamera", "app.videoPreview.webcamPreviewLabel": "Veebikaamera eelvaade", "app.videoPreview.webcamSettingsTitle": "Veebikaamera seaded", @@ -554,7 +576,7 @@ "app.video.notFoundError": "Veebikaamerat ei leitud. Palun kontrolli, kas see on ühendatud", "app.video.notAllowed": "Veebikaamera jagamiseks puuduvad õigused. Palun kontrolli veebilehitsejas jagamisõigusi", "app.video.notSupportedError": "Veebikaamera videot saab jagada vaid üle turvalise ühenduse. Veendu, et su SSL sertifikaat on kehtiv.", - "app.video.notReadableError": "Veebikaamera videot ei õnnestunud kätte saada. Palun kontrolli, et ükski teine programm veebikaamerat samal ajal ei kasuta.", + "app.video.notReadableError": "Veebikaamera videot ei õnnestunud kätte saada. Palun kontrolli, et ükski teine programm veebikaamerat samal ajal ei kasuta", "app.video.mediaFlowTimeout1020": "Meedia ei saanud serveriga ühendust (viga 1020)", "app.video.suggestWebcamLock": "Kas jõustada vaatajate veebikaamerate lukustamine?", "app.video.suggestWebcamLockReason": "(see parandab sessiooni stabiilsust)", @@ -569,19 +591,6 @@ "app.video.videoMenuDesc": "Ava videomenüü", "app.video.chromeExtensionError": "Sa pead installeerima", "app.video.chromeExtensionErrorLink": "selle Chrome'i laienduse", - "app.video.stats.title": "Ühenduse statistika", - "app.video.stats.packetsReceived": "Vastuvõetud pakette", - "app.video.stats.packetsSent": "Saadetud pakette", - "app.video.stats.packetsLost": "Kaotatud pakette", - "app.video.stats.bitrate": "Bitikiirus", - "app.video.stats.lostPercentage": "Kaotusprotsent kokku", - "app.video.stats.lostRecentPercentage": "Kaotusprotsent hiljuti", - "app.video.stats.dimensions": "Mõõdud", - "app.video.stats.codec": "Koodek", - "app.video.stats.decodeDelay": "Dekodeerimise viivitus", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Kodeerija kasutus", - "app.video.stats.currentDelay": "Praegune viivitus", "app.fullscreenButton.label": "Esita {0} täisekraanil", "app.deskshare.iceConnectionStateError": "Ühendumine ebaõnnestus ekraani jagamisel (ICE viga 1108)", "app.sfu.mediaServerConnectionError2000": "Meediaserveriga ei saa ühendust (viga 2000)", @@ -594,7 +603,6 @@ "app.sfu.invalidSdp2202":"Klient genereeris vigase meediapäringu (SDP viga 2202)", "app.sfu.noAvailableCodec2203": "Server ei leidnud sobivat koodekit (viga 2203)", "app.meeting.endNotification.ok.label": "Ok", - "app.whiteboard.annotations.poll": "Küsitluse tulemused avalikustati", "app.whiteboard.toolbar.tools": "Tööriistad", "app.whiteboard.toolbar.tools.hand": "Liiguta", "app.whiteboard.toolbar.tools.pencil": "Pliiats", @@ -675,10 +683,10 @@ "app.externalVideo.autoPlayWarning": "Käivita video, et meedia sünkroniseerimine aktiveerida", "app.network.connection.effective.slow": "Märkasime, et ühendusega on probleeme.", "app.network.connection.effective.slow.help": "Rohkem infot", - "app.externalVideo.noteLabel": "Märkus: jagatud väliseid videoid sessiooni salvestisse ei kaasata. Toetatakse linke YouTube'i, Vimeo, Instructure Media, Twitchi ja Daily Motioni videotele.", + "app.externalVideo.noteLabel": "Märkus: jagatud väliseid videoid sessiooni salvestisse ei kaasata. Toetatatakse keskkondi YouTube, Vimeo, Instructure Media, Twitch ja Daily Motion ning linke meediafailidele (nt https://example.com/xy.mp4).", "app.actionsBar.actionsDropdown.shareExternalVideo": "Jaga välist videot", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Lõpeta välise video jagamine", - "app.iOSWarning.label": "Palun uuenda iOS versioonile 12.2 või hilisemale", + "app.iOSWarning.label": "Palun uuenda iOS versiooniks 12.2 või hilisemaks", "app.legacy.unsupportedBrowser": "Paistab, et kasutad veebilehitsejat, mida ei toetata. Täielikuks toetuseks kasuta palun brauserit {0} või {1}.", "app.legacy.upgradeBrowser": "Paistab, et kasutad toetatud veebilehitseja vanemat versiooni. Täielikuks toetuseks uuenda palun oma veebilehitsejat.", "app.legacy.criosBrowser": "Täielikuks toetuseks kasuta palun iOSis Safarit." diff --git a/bigbluebutton-html5/private/locales/eu.json b/bigbluebutton-html5/private/locales/eu.json index 35f5a2ac2b4f1781e1d9f51367d24f2955efab7b..2385d4074f3b92e53268a4c63e3b8497929a646f 100644 --- a/bigbluebutton-html5/private/locales/eu.json +++ b/bigbluebutton-html5/private/locales/eu.json @@ -50,10 +50,10 @@ "app.note.title": "Ohar Partekatuak", "app.note.label": "Oharra", "app.note.hideNoteLabel": "Ezkutatu oharra", + "app.note.tipLabel": "Sakatu Esc edizioaren tresna-barra fokuratzeko", "app.user.activityCheck": "Erabiltzailearen aktibitate-kontrola", "app.user.activityCheck.label": "Egiaztatu erabiltzailea oraindik bileran dagoen ({0})", "app.user.activityCheck.check": "Egiaztatu", - "app.note.tipLabel": "Sakatu Esc edizioaren tresna-barra fokuratzeko", "app.userList.usersTitle": "Erabiltzaileak", "app.userList.participantsTitle": "Parte-hartzaileak", "app.userList.messagesTitle": "Mezuak", @@ -126,8 +126,10 @@ "app.meeting.meetingTimeRemaining": "Bilerari geratzen zaion denbora: {0}", "app.meeting.meetingTimeHasEnded": "Denbora agortu da. Bilera laster itxiko da.", "app.meeting.endedMessage": "Hasierako pantailara itzuliko zara", - "app.meeting.alertMeetingEndsUnderOneMinute": "Bilera minutu batean itxiko da.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Banatze-taldeko bilera minutu batean itxiko da.", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "Bilera minutu bat barru itxiko da.", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "Bilera {0} minutu barru itxiko da.", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "Banatze-taldea {0} minutu barru itxiko da.", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "Banatze-taldea minutu bat barru itxiko da.", "app.presentation.hide": "Ezkutatu aurkezpena", "app.presentation.notificationLabel": "Uneko aurkezpena", "app.presentation.slideContent": "Diapositiba aurkezpena", @@ -267,7 +269,7 @@ "app.leaveConfirmation.confirmLabel": "Irten", "app.leaveConfirmation.confirmDesc": "Bileratik kanporatzen zaitu", "app.endMeeting.title": "Amaitu bilera", - "app.endMeeting.description": "Ziur zaude bilera amaitu nahi duzula?", + "app.endMeeting.description": "Ziur zaude bilera hau partaide guztientzako amaitu nahi duzula? Partaide guztiak deskonektatuko dira.", "app.endMeeting.yesLabel": "Bai", "app.endMeeting.noLabel": "Ez", "app.about.title": "Honi buruz", @@ -288,10 +290,6 @@ "app.screenshare.screenShareLabel" : "Pantaila partekatzea", "app.submenu.application.applicationSectionTitle": "Aplikazioa", "app.submenu.application.animationsLabel": "Animazioak", - "app.submenu.application.audioAlertLabel": "Txataren audio-alarmak", - "app.submenu.application.pushAlertLabel": "Txataren jakinarazpen-alarmak", - "app.submenu.application.userJoinAudioAlertLabel": "Audio alerta erabiltzailea sartzean", - "app.submenu.application.userJoinPushAlertLabel": "Alerta ireki erabiltzailea sartzean", "app.submenu.application.fontSizeControlLabel": "Letra tamaina", "app.submenu.application.increaseFontBtnLabel": "Handitu aplikazioaren letra", "app.submenu.application.decreaseFontBtnLabel": "Txikitu aplikazioaren letra", @@ -573,19 +571,6 @@ "app.video.videoMenuDesc": "Ireki bideoaren goitik-beherako menua", "app.video.chromeExtensionError": "Instalatu behar duzu", "app.video.chromeExtensionErrorLink": "Chromeren gehigarri hau", - "app.video.stats.title": "Konexioen estatistikak", - "app.video.stats.packetsReceived": "Jasotako paketeak", - "app.video.stats.packetsSent": "Bidalitako paketeak", - "app.video.stats.packetsLost": "Galdutako paketeak", - "app.video.stats.bitrate": "Bit-emaria", - "app.video.stats.lostPercentage": "Guztira galdutakoen ehunekoa", - "app.video.stats.lostRecentPercentage": "Duela gutxi galdutakoen ehunekoa", - "app.video.stats.dimensions": "Neurriak", - "app.video.stats.codec": "Kodeketa", - "app.video.stats.decodeDelay": "Dekodetze-atzerapena", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Kodetzearen erabilera", - "app.video.stats.currentDelay": "Uneko atzerapena", "app.fullscreenButton.label": "Jarri {0} pantaila osoan", "app.deskshare.iceConnectionStateError": "Konexioak huts egin du pantaila partekatzean (1108 ICE errorea)", "app.sfu.mediaServerConnectionError2000": "Ezin da konektatu multimedia zerbitzaria (2000 errorea)", @@ -598,7 +583,6 @@ "app.sfu.invalidSdp2202":"Bezeroak baliozkoa ez den multimedia eskaera bat sortu du (2202 errorea)", "app.sfu.noAvailableCodec2203": "Zerbitzariak ezin du aurkitu kodeketa egokia (2203 errorea)", "app.meeting.endNotification.ok.label": "Ados", - "app.whiteboard.annotations.poll": "Inkestaren emaitzak argitaratu dira", "app.whiteboard.toolbar.tools": "Tresnak", "app.whiteboard.toolbar.tools.hand": "Eskua", "app.whiteboard.toolbar.tools.pencil": "Arkatza", @@ -679,7 +663,6 @@ "app.externalVideo.autoPlayWarning": "Erreproduzitu bideoa multimedia-sinkronizazioa gaitzeko", "app.network.connection.effective.slow": "Konexio arazoak antzematen ari gara", "app.network.connection.effective.slow.help": "Informazio gehiago", - "app.externalVideo.noteLabel": "Oharra: kanpoko bideo partekatuak ez dira grabazioan agertuko. YouTube, Vimeo, Instructure Media, Twitch eta Daily Motion webguneetako URLak onartzen dira.", "app.actionsBar.actionsDropdown.shareExternalVideo": "Partekatu kanpoko bideo bat", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Gelditu kanpoko bideoaren partekatzea", "app.iOSWarning.label": "Eguneratu iOS 12.2 edo berriagora", diff --git a/bigbluebutton-html5/private/locales/fa_IR.json b/bigbluebutton-html5/private/locales/fa_IR.json index 0247ed5ed0c3c72829765ec9a28d812081683067..8912c175b761eccdb24c98be2ee3bfecf67154c0 100644 --- a/bigbluebutton-html5/private/locales/fa_IR.json +++ b/bigbluebutton-html5/private/locales/fa_IR.json @@ -1,23 +1,24 @@ { - "app.home.greeting": "ارائه شما به زودی آغاز خواهد شد ...", + "app.home.greeting": "ارائه شما به زودی آغاز خواهد شد...", "app.chat.submitLabel": "ارسال پیام", - "app.chat.errorMaxMessageLength": "پیام {0} کاراکتر(ÛŒ) بسیار بلند است", - "app.chat.disconnected": "ارتباط شما قطع شده است، امکان ارسال پیام ها وجود ندارد", - "app.chat.locked": "Ú¯ÙÙ†Ú¯Ùˆ Ù‚ÙÙ„ شده است، امکان ارسال پیام ها وجود ندارد", - "app.chat.inputLabel": "ورودی پیام برای چت {0}", + "app.chat.errorMaxMessageLength": "پیام {0} کاراکتری بسیار بلند است", + "app.chat.disconnected": "ارتباط شما قطع شده است، امکان ارسال پیام‌ها وجود ندارد", + "app.chat.locked": "Ú¯ÙÙ†Ú¯Ùˆ Ù‚ÙÙ„ شده است، امکان ارسال هیچ پیامی وجود ندارد", + "app.chat.inputLabel": "ورودی پیام برای Ú¯ÙØªÚ¯Ùˆ {0}", "app.chat.inputPlaceholder": "ارسال پیام به {0}", "app.chat.titlePublic": "Ú¯ÙØªÚ¯ÙˆÛŒ عمومی", "app.chat.titlePrivate": "Ú¯ÙØªÚ¯ÙˆÛŒ خصوصی با {0}", "app.chat.partnerDisconnected": "{0} جلسه را ترک کرد", "app.chat.closeChatLabel": "خروج {0}", "app.chat.hideChatLabel": "پنهان سازی {0}", - "app.chat.moreMessages": "ادامه پیام ها در پایین", - "app.chat.dropdown.options": "گزینه های Ú¯ÙØªÚ¯Ùˆ", + "app.chat.moreMessages": "ادامه پیام‌ها در پایین", + "app.chat.dropdown.options": "گزینه‌های Ú¯ÙØªÚ¯Ùˆ", "app.chat.dropdown.clear": "پاکسازی", "app.chat.dropdown.copy": "Ú©Ù¾ÛŒ", "app.chat.dropdown.save": "ذخیره", "app.chat.label": "Ú¯ÙØªÚ¯Ùˆ", "app.chat.offline": "Ø¢Ùلاین", + "app.chat.pollResult": "نتایج نظرسنجی", "app.chat.emptyLogLabel": "پاک کردن سابقه Ú¯ÙØªÚ¯Ùˆ", "app.chat.clearPublicChatMessage": "سابقه Ú¯ÙØªÚ¯ÙˆÙ‡Ø§ توسط مدیر ØØ°Ù گردید", "app.chat.multi.typing": "چند کاربر در ØØ§Ù„ نوشتن هستند", @@ -33,43 +34,43 @@ "app.captions.menu.subtitle": "Ù„Ø·ÙØ§ در جلسه خود یک زبان Ùˆ سبک برای زیرنویس ها انتخاب کنید", "app.captions.menu.title": "زیر نویس ها", "app.captions.menu.fontSize": "اندازه", - "app.captions.menu.fontColor": "زنگ متن", + "app.captions.menu.fontColor": "رنگ متن", "app.captions.menu.fontFamily": "قلم", - "app.captions.menu.backgroundColor": "رنگ پس زمینه", - "app.captions.menu.previewLabel": "پیش نمایش", + "app.captions.menu.backgroundColor": "رنگ پس‌زمینه", + "app.captions.menu.previewLabel": "پیش‌نمایش", "app.captions.menu.cancelLabel": "لغو", "app.captions.pad.hide": "پنهان سازی زیرنویس", - "app.captions.pad.tip": "برای ÙØ¹Ø§Ù„ کردن ادیتور نوار ابزار Esc را ÙØ´Ø§Ø± دهید", + "app.captions.pad.tip": "برای ÙØ¹Ø§Ù„ کردن ویرایشگر نوار ابزار، Esc را ÙØ´Ø§Ø± دهید", "app.captions.pad.ownership": "Ú¯Ø±ÙØªÙ† کنترل", - "app.captions.pad.ownershipTooltip": "شما به عنوان ØµØ§ØØ¨ زیر نویس های {0} شناخته خواهید شد", + "app.captions.pad.ownershipTooltip": "شما به عنوان ØµØ§ØØ¨ زیر نویس های {0} منسوب خواهید شد", "app.captions.pad.interimResult": "Ù†ØªØ§ÛŒØ Ù…ÙˆÙ‚Øª", "app.captions.pad.dictationStart": "آغاز نوشتن کلمات", "app.captions.pad.dictationStop": "توق٠نوشتن کلمات", "app.captions.pad.dictationOnDesc": "روشن کردن امکان تشخیص صوت", "app.captions.pad.dictationOffDesc": "غیر ÙØ¹Ø§Ù„ کردن امکان تشخیص صوت", - "app.note.title": "یادداشت های اشتراکی", + "app.note.title": "یادداشت‌های اشتراکی", "app.note.label": "یادداشت", "app.note.hideNoteLabel": "پنهان کردن یادداشت", + "app.note.tipLabel": "برای ÙØ¹Ø§Ù„ کردن ویرایشگر نوار ابزار، Esc را ÙØ´Ø§Ø± دهید", "app.user.activityCheck": "بررسی ÙØ¹Ø§Ù„یت کاربر", "app.user.activityCheck.label": "بررسی کنید آیا کاربر هنوز در جلسه ({0}) ØØ¶ÙˆØ± دارد", "app.user.activityCheck.check": "بررسی", - "app.note.tipLabel": "برای ÙØ¹Ø§Ù„ کردن ادیتور نوار ابزار Esc را ÙØ´Ø§Ø± دهید", "app.userList.usersTitle": "کاربران", "app.userList.participantsTitle": "شرکت کنندگان", - "app.userList.messagesTitle": "پیام ها", - "app.userList.notesTitle": "یادداشت ها", - "app.userList.notesListItem.unreadContent": "Ù…ØØªÙˆØ§ÛŒ جدید در بخش یادداشت های اشتراکی وجود دارد.", + "app.userList.messagesTitle": "پیام‌ها", + "app.userList.notesTitle": "یادداشت‌ها", + "app.userList.notesListItem.unreadContent": "Ù…ØØªÙˆØ§ÛŒ جدید در بخش یادداشت‌های اشتراکی وجود دارد.", "app.userList.captionsTitle": "عناوین", "app.userList.presenter": "ارائه دهنده", "app.userList.you": "شما", "app.userList.locked": "Ù‚ÙÙ„ شده", - "app.userList.byModerator": "توسط (Moderator)", + "app.userList.byModerator": "توسط (مدیر)", "app.userList.label": "لیست کاربر", "app.userList.toggleCompactView.label": "تغییر در ØØ§Ù„ت نمایه ÙØ´Ø±Ø¯Ù‡", "app.userList.guest": "مهمان", - "app.userList.menuTitleContext": "گزینه های موجود", + "app.userList.menuTitleContext": "گزینه‌های موجود", "app.userList.chatListItem.unreadSingular": "{0} پیام جدید", - "app.userList.chatListItem.unreadPlural": "{0} پیام های جدید", + "app.userList.chatListItem.unreadPlural": "{0} پیام‌ جدید", "app.userList.menu.chat.label": "شروع Ú¯ÙØªÚ¯ÙˆÛŒ خصوصی", "app.userList.menu.clearStatus.label": "پاک کردن وضعیت", "app.userList.menu.removeUser.label": "ØØ°Ù کاربر", @@ -86,30 +87,32 @@ "app.userList.menu.makePresenter.label": "تغییر نقش به ارائه دهنده", "app.userList.userOptions.manageUsersLabel": "مدیریت کاربران", "app.userList.userOptions.muteAllLabel": "بستن صدای همه کاربران", - "app.userList.userOptions.muteAllDesc": "بستن صدای همه کاربران جاضر در جلسه", - "app.userList.userOptions.clearAllLabel": "پاک کردن آیکون همه وضعیت ها", - "app.userList.userOptions.clearAllDesc": "پاک کردن همه آیکون های وضعیت کاربران", + "app.userList.userOptions.muteAllDesc": "بستن صدای همه کاربران ØØ§Ø¶Ø± در جلسه", + "app.userList.userOptions.clearAllLabel": "پاک کردن آیکون همه وضعیت‌ها", + "app.userList.userOptions.clearAllDesc": "پاک کردن همه آیکون‌های وضعیت از کاربران", "app.userList.userOptions.muteAllExceptPresenterLabel": "بستن صدای همه کاربران به جز ارائه دهنده", "app.userList.userOptions.muteAllExceptPresenterDesc": "بستن صدای همه کاربران در جلسه به جز ارائه دهنده", "app.userList.userOptions.unmuteAllLabel": "ØºÛŒØ±ÙØ¹Ø§Ù„ کردن امکان بستن صدای جلسه", "app.userList.userOptions.unmuteAllDesc": "صدای جلسه را باز میکند", "app.userList.userOptions.lockViewersLabel": "Ù‚ÙÙ„ کردن کاربران", "app.userList.userOptions.lockViewersDesc": "ØºÛŒØ±ÙØ¹Ø§Ù„ کردن امکانات خاص برای شرکت کنندگان در جلسه", - "app.userList.userOptions.disableCam": "اشتراک گذاری دوربین کاربران غیر ÙØ¹Ø§Ù„ شده است", - "app.userList.userOptions.disableMic": "Ø§Ø³ØªÙØ§Ø¯Ù‡ از امکان صدا برای کاربران غیر ÙØ¹Ø§Ù„ شده است", - "app.userList.userOptions.disablePrivChat": "Ú¯ÙØªÚ¯ÙˆÛŒ خصوصی غیر ÙØ¹Ø§Ù„ شده است", - "app.userList.userOptions.disablePubChat": "Ú¯ÙØªÚ¯ÙˆÛŒ عمومی غیر ÙØ¹Ø§Ù„ شده است", + "app.userList.userOptions.connectionStatusLabel": "وضعیت اتصال", + "app.userList.userOptions.connectionStatusDesc": "وضعیت اتصال کاربران را مشاهده کنید", + "app.userList.userOptions.disableCam": "اشتراک گذاری دوربین کاربران ØºÛŒØ±ÙØ¹Ø§Ù„ شده است", + "app.userList.userOptions.disableMic": "Ø§Ø³ØªÙØ§Ø¯Ù‡ از امکان صدا برای کاربران ØºÛŒØ±ÙØ¹Ø§Ù„ شده است", + "app.userList.userOptions.disablePrivChat": "Ú¯ÙØªÚ¯ÙˆÛŒ خصوصی ØºÛŒØ±ÙØ¹Ø§Ù„ شده است", + "app.userList.userOptions.disablePubChat": "Ú¯ÙØªÚ¯ÙˆÛŒ عمومی ØºÛŒØ±ÙØ¹Ø§Ù„ شده است", "app.userList.userOptions.disableNote": "یادداشت اشتراکی در ØØ§Ù„ ØØ§Ø¶Ø± Ù‚ÙÙ„ شده است", "app.userList.userOptions.hideUserList": "لیست کاربران در ØØ§Ù„ ØØ§Ø¶Ø± برای شرکت کنندگان قابل مشاهده نیست", - "app.userList.userOptions.webcamsOnlyForModerator": "تنها مدیران امکان مشاهده دوربین های کاربران را دارند (به دلیل تنظیمات Ù‚ÙÙ„)", + "app.userList.userOptions.webcamsOnlyForModerator": "تنها مدیران امکان مشاهده دوربین‌های کاربران را دارند (به دلیل تنظیمات Ù‚ÙÙ„)", "app.userList.content.participants.options.clearedStatus": "وضعیت همه کاربرها پاک شد", "app.userList.userOptions.enableCam": "دوربین کاربران ÙØ¹Ø§Ù„ شد", "app.userList.userOptions.enableMic": "میکروÙون کاربران ÙØ¹Ø§Ù„ شد", - "app.userList.userOptions.enablePrivChat": "Ú¯ÙØªÚ¯ÙˆÛŒ متنی خصوصی ÙØ¹Ø§Ù„ شد", - "app.userList.userOptions.enablePubChat": "Ú¯ÙØªÚ¯ÙˆÛŒ متنی عمومی ÙØ¹Ø§Ù„ شد", - "app.userList.userOptions.enableNote": "یادداشت های اشتراکی ÙØ¹Ø§Ù„ شد", + "app.userList.userOptions.enablePrivChat": "Ú¯ÙØªÚ¯ÙˆÛŒ خصوصی ÙØ¹Ø§Ù„ شد", + "app.userList.userOptions.enablePubChat": "Ú¯ÙØªÚ¯ÙˆÛŒ عمومی ÙØ¹Ø§Ù„ شد", + "app.userList.userOptions.enableNote": "یادداشت‌های اشتراکی ÙØ¹Ø§Ù„ شد", "app.userList.userOptions.showUserList": "لیست کاربران در ØØ§Ù„ ØØ§Ø¶Ø± برای شرکت کنندگان قابل مشاهده است", - "app.userList.userOptions.enableOnlyModeratorWebcam": "شما در ØØ§Ù„ ØØ§Ø¶Ø± میتوانید دوربین خود را به اشتراک بگذارید، همه تصویر شما را خواهند دید", + "app.userList.userOptions.enableOnlyModeratorWebcam": "شما در ØØ§Ù„ ØØ§Ø¶Ø± می‌توانید دوربین خود را به اشتراک بگذارید، همه تصویر شما را خواهند دید", "app.media.label": "صدا Ùˆ تصویر", "app.media.autoplayAlertDesc": "دادن اجازه دسترسی", "app.media.screenshare.start": "اشتراک ØµÙØÙ‡ نمایش شروع شد", @@ -124,12 +127,15 @@ "app.screenshare.genericError": "خطایی در هنگام به اشتراک گذاری ØµÙØÙ‡ نمایش پیش آمده ØŒ Ù„Ø·ÙØ§ دوباره تلاش کنید.", "app.meeting.ended": "جلسه پایان ÛŒØ§ÙØª", "app.meeting.meetingTimeRemaining": "زمان باقی مانده از جلسه: {0}", - "app.meeting.meetingTimeHasEnded": "زمان جلسه به اتمام رسید. جلسه در اسرع وقت بسته خواهد شد", + "app.meeting.meetingTimeHasEnded": "زمان جلسه به اتمام رسید. جلسه به زودی بسته خواهد شد", "app.meeting.endedMessage": "شما در ØØ§Ù„ انتقال به ØµÙØÙ‡ اصلی هستید", - "app.meeting.alertMeetingEndsUnderOneMinute": "جلسه ظر٠یک دقیقه آینده بسته خواهد شد.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "جلسه ظر٠یک دقیقه آینده به اتمام خواهد رسید.", - "app.presentation.hide": "پنهان سازی ارائه", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "جلسه تا یک دقیقه دیگر به پایان می‌رسد.", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "جلسه تا {0} دقیقه دیگر به پایان می‌رسد.", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "اتمام جلسه زیر مجموعه تا {0} دقیقه دیگر", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "اتمام جلسه زیر مجموعه تا یک دقیقه دیگر", + "app.presentation.hide": "پنهان‌سازی ارائه", "app.presentation.notificationLabel": "ارائه کنونی", + "app.presentation.downloadLabel": "دانلود", "app.presentation.slideContent": "Ù…ØØªÙˆØ§ÛŒ اسلاید", "app.presentation.startSlideContent": "شروع Ù…ØØªÙˆØ§ÛŒ اسلاید", "app.presentation.endSlideContent": "انتهای Ù…ØØªÙˆØ§ÛŒ اسلاید", @@ -147,100 +153,109 @@ "app.presentation.presentationToolbar.fitWidthDesc": "کلیه عرض اسلاید را نمایش بده", "app.presentation.presentationToolbar.fitScreenLabel": "ابعاد را به اندازه تمام ØµÙØÙ‡ Ú©Ù†", "app.presentation.presentationToolbar.fitScreenDesc": "Ú©Ù„ اسلاید را نمایش بده", - "app.presentation.presentationToolbar.zoomLabel": "بزرگ نمایی", - "app.presentation.presentationToolbar.zoomDesc": "تغییر مقدار زوم ارائه", - "app.presentation.presentationToolbar.zoomInLabel": "Ø§ÙØ²Ø§ÛŒØ´ بزرگنمایی", - "app.presentation.presentationToolbar.zoomInDesc": "Ø§ÙØ²Ø§ÛŒØ´ بزرگ نمایی ارائه", - "app.presentation.presentationToolbar.zoomOutLabel": "کاهش بزرگ نمایی", - "app.presentation.presentationToolbar.zoomOutDesc": "کاهش بزرگ نمایی ارائه", - "app.presentation.presentationToolbar.zoomReset": "بازنشانی بزرگ نمایی", - "app.presentation.presentationToolbar.zoomIndicator": "درصد بزرگ نمایی کنونی", + "app.presentation.presentationToolbar.zoomLabel": "بزرگ‌نمایی", + "app.presentation.presentationToolbar.zoomDesc": "تغییر مقدار بزرگ‌نمایی ارائه", + "app.presentation.presentationToolbar.zoomInLabel": "Ø§ÙØ²Ø§ÛŒØ´ بزرگ‌نمایی", + "app.presentation.presentationToolbar.zoomInDesc": "Ø§ÙØ²Ø§ÛŒØ´ بزرگ‌نمایی ارائه", + "app.presentation.presentationToolbar.zoomOutLabel": "کاهش بزرگ‌نمایی", + "app.presentation.presentationToolbar.zoomOutDesc": "کاهش بزرگ‌نمایی ارائه", + "app.presentation.presentationToolbar.zoomReset": "بازنشانی بزرگ‌نمایی", + "app.presentation.presentationToolbar.zoomIndicator": "درصد بزرگ‌نمایی کنونی", "app.presentation.presentationToolbar.fitToWidth": "اندازه تصویر را متناسب با عرض ارائه Ú©Ù†", "app.presentation.presentationToolbar.fitToPage": "اندازه تصویر را متناسب با عرض ØµÙØÙ‡ Ú©Ù†", "app.presentation.presentationToolbar.goToSlide": "اسلاید {0}", "app.presentationUploder.title": "ارائه", - "app.presentationUploder.message": "به عنوان یک ارائه دهنده شما قادرید انواع ÙØ§ÛŒÙ„ های مجموعه Ø¢Ùیس Ùˆ یا ÙØ§ÛŒÙ„ pdf را بارگذاری نمایید\nپیشنهاد ما ارائه با ÙØ§ÛŒÙ„ pdf میباشد.Ù„Ø·ÙØ§ از انتخاب بودن ارائه مورد نظر در سمت راست اطمینان ØØ§ØµÙ„ نمایید.", - "app.presentationUploder.uploadLabel": "بارگزاری", + "app.presentationUploder.message": "به عنوان یک ارائه دهنده شما قادرید انواع ÙØ§ÛŒÙ„ های مجموعه Ø¢Ùیس Ùˆ یا ÙØ§ÛŒÙ„ pdf را بارگذاری نمایید پیشنهاد ما ارائه با ÙØ§ÛŒÙ„ pdf میباشد.Ù„Ø·ÙØ§ از انتخاب بودن ارائه مورد نظر در سمت راست اطمینان ØØ§ØµÙ„ نمایید.", + "app.presentationUploder.uploadLabel": "بارگذاری", "app.presentationUploder.confirmLabel": "تایید", "app.presentationUploder.confirmDesc": "تغییرات خود را ذخیره کنید Ùˆ ارائه را آغاز نمایید.", "app.presentationUploder.dismissLabel": "لغو", "app.presentationUploder.dismissDesc": "بستن پنجره Ùˆ ØØ°Ù تغییرات شما", - "app.presentationUploder.dropzoneLabel": "ÙØ§ÛŒÙ„ های خود را برای آپلود کشیده Ùˆ در اینجا رها کنید", - "app.presentationUploder.dropzoneImagesLabel": "تصاویر را به منظور بارگزاری اینجا رها کنید", + "app.presentationUploder.dropzoneLabel": "پرونده‌های خود را برای بارگذاری کشیده Ùˆ در اینجا رها کنید", + "app.presentationUploder.dropzoneImagesLabel": "تصاویر را به منظور بارگذاری اینجا رها کنید", "app.presentationUploder.browseFilesLabel": "یا برای انتخاب ÙØ§ÛŒÙ„ کلیک کنید.", - "app.presentationUploder.browseImagesLabel": "یا عکس ها را بروز/بگیر", - "app.presentationUploder.fileToUpload": "آماده بارگزاری ...", + "app.presentationUploder.browseImagesLabel": "یا تصاویر را مرور/بگیرید", + "app.presentationUploder.fileToUpload": "آماده بارگذاری ...", "app.presentationUploder.currentBadge": "کنونی", - "app.presentationUploder.rejectedError": "ÙØ§ÛŒÙ„(های) انتخاب شده رد شدند. Ù„Ø·ÙØ§ نوع ÙØ§ÛŒÙ„(ها) را بررسی کنید", - "app.presentationUploder.upload.progress": "در ØØ§Ù„ بارگزاری ({0}%)", - "app.presentationUploder.upload.413": "ØØ¬Ù… ÙØ§ÛŒÙ„ زیاد است.Ù„Ø·ÙØ§ آن را به چند ÙØ§ÛŒÙ„ کوچکتر تبدیل کنید", + "app.presentationUploder.rejectedError": "پرونده(های) انتخاب شده رد شدند. Ù„Ø·ÙØ§ نوع پرونده(ها) را بررسی کنید.", + "app.presentationUploder.upload.progress": "در ØØ§Ù„ بارگذاری ({0}%)", + "app.presentationUploder.upload.413": "ØØ¬Ù… پرونده زیاد است. Ù„Ø·ÙØ§ آن را به چند ÙØ§ÛŒÙ„ کوچک‌تر تبدیل کنید.", + "app.presentationUploder.genericError": "آخ، خطای پیش آمده است...", "app.presentationUploder.upload.408": "زمان درخواست شناسه بارگذاری به پایان رسید.", - "app.presentationUploder.upload.404": "404: شناسه بارگذاری نامعتبر Ù…ÛŒ باشد.", + "app.presentationUploder.upload.404": "Û´Û°Û´: شناسه بارگذاری نامعتبر می‌باشد.", "app.presentationUploder.upload.401": "درخواست شناسه بارگذاری ارائه ناموÙÙ‚ بوده است.", "app.presentationUploder.conversion.conversionProcessingSlides": "در ØØ§Ù„ پردازش ØµÙØÙ‡ {0} از {1}", - "app.presentationUploder.conversion.genericConversionStatus": "در ØØ§Ù„ تبدیل ÙØ§ÛŒÙ„ ...", + "app.presentationUploder.conversion.genericConversionStatus": "در ØØ§Ù„ تبدیل پرونده ...", "app.presentationUploder.conversion.generatingThumbnail": "در ØØ§Ù„ تولید تصاویر Ú©ÙˆÚ†Ú© ...", - "app.presentationUploder.conversion.generatedSlides": "اسلاید ها تولید شدند ...", + "app.presentationUploder.conversion.generatedSlides": "اسلایدها تولید شدند ...", "app.presentationUploder.conversion.generatingSvg": "در ØØ§Ù„ تولید تصاویر SVG ...", - "app.presentationUploder.conversion.pageCountExceeded": "تعداد ØµÙØØ§Øª بیشتر از مقدار مجاز شده است، Ù„Ø·ÙØ§ به چند ÙØ§ÛŒÙ„ تقسیم کنید.", - "app.presentationUploder.conversion.officeDocConversionInvalid": "خطا در پردازش مستند اÙیس، Ù„Ø·ÙØ§ به جای آن، ÙØ§ÛŒÙ„ PDF آپلود کنید.", - "app.presentationUploder.conversion.officeDocConversionFailed": "خطا در پردازش مستند اÙیس، Ù„Ø·ÙØ§ به جای آن، ÙØ§ÛŒÙ„ PDF آپلود کنید.", - "app.presentationUploder.conversion.pdfHasBigPage": "تبدیل ÙØ§ÛŒÙ„ pdf موÙÙ‚ نبود. Ù„Ø·ÙØ§ ÙØ§ÛŒÙ„ خود را ویرایش کنید.", - "app.presentationUploder.conversion.timeout": "اوپس، عملیات تبدیل خیلی طول کشید", + "app.presentationUploder.conversion.pageCountExceeded": "تعداد ØµÙØØ§Øª بیشتر از مقدار مجاز شده است، Ù„Ø·ÙØ§ به چند پرونده تقسیم کنید.", + "app.presentationUploder.conversion.officeDocConversionInvalid": "خطا در پردازش اسناد Ø¢Ùیس، Ù„Ø·ÙØ§ به جای آن، پرونده PDF بارگذاری کنید.", + "app.presentationUploder.conversion.officeDocConversionFailed": "خطا در پردازش اسناد Ø¢Ùیس، Ù„Ø·ÙØ§ به جای آن، پرونده PDF بارگذاری کنید.", + "app.presentationUploder.conversion.pdfHasBigPage": "تبدیل پرونده PDF موÙÙ‚ نبود. Ù„Ø·ÙØ§ پرونده خود را بهینه‌سازی کنید.", + "app.presentationUploder.conversion.timeout": "آخ، عملیات تبدیل خیلی طول کشید", "app.presentationUploder.conversion.pageCountFailed": "خطا در مشخص کردن تعداد ØµÙØØ§Øª مستند.", "app.presentationUploder.isDownloadableLabel": "مجوز دانلود ارائه را نده", "app.presentationUploder.isNotDownloadableLabel": "مجوز دانلود ارائه را بده", "app.presentationUploder.removePresentationLabel": "ØØ°Ù ارائه", "app.presentationUploder.setAsCurrentPresentation": "انتخاب ارائه به عنوان ارائه ÙØ¹Ø§Ù„", - "app.presentationUploder.tableHeading.filename": "نام ÙØ§ÛŒÙ„", - "app.presentationUploder.tableHeading.options": "گزینه ها", + "app.presentationUploder.tableHeading.filename": "نام پرونده", + "app.presentationUploder.tableHeading.options": "گزینه‌ها", "app.presentationUploder.tableHeading.status": "وضعیت", + "app.presentationUploder.uploading": "در ØØ§Ù„ بارگذاری {0} {1}", + "app.presentationUploder.uploadStatus": "{0} از {1} بارگذاری کامل شد", + "app.presentationUploder.completed": "{0} بارگذاری کامل شد", + "app.presentationUploder.item" : "آیتم", + "app.presentationUploder.itemPlural" : "آیتم‌ها", + "app.presentationUploder.clearErrors": "پاک کردن خطاها", + "app.presentationUploder.clearErrorsDesc": "پاک کردن بارگذاری‌های ناموÙÙ‚ ارائه", "app.poll.pollPaneTitle": "نظرسنجی", "app.poll.quickPollTitle": "نظرسنجی سریع", - "app.poll.hidePollDesc": "پنهان سازی منوی نظرسنجی", - "app.poll.customPollInstruction": "برای ایجاد نظرسنجی شخصی، دگمه زیر را انتخاب کرده Ùˆ گزینه های خود را وارد کنید.", - "app.poll.quickPollInstruction": "برای شروع نظرسنجی خود گزینه زیر را انتخاب کنید", - "app.poll.customPollLabel": "نظرسنجی شخصی", - "app.poll.startCustomLabel": "آغاز نظرسنجی شخصی", - "app.poll.activePollInstruction": "برای مشاهده نتایج نظر سنجی ØŒ این پنجره را باز Ù†Ú¯Ù‡ دارید.\nدر زمان مورد نظر میتوانید با کلیک بر روی دکمه انتشار نتایج... نتایج نظر سنجی را با کاربران خود به اشتراک بگذارید.", + "app.poll.hidePollDesc": "پنهان‌سازی منوی نظرسنجی", + "app.poll.customPollInstruction": "برای ایجاد نظرسنجی Ø³ÙØ§Ø±Ø´ÛŒØŒ دکمه زیر را انتخاب کرده Ùˆ گزینه‌های خود را وارد کنید.", + "app.poll.quickPollInstruction": "برای شروع نظرسنجی خود، گزینه زیر را انتخاب کنید.", + "app.poll.customPollLabel": "نظرسنجی Ø³ÙØ§Ø±Ø´ÛŒ", + "app.poll.startCustomLabel": "آغاز نظرسنجی Ø³ÙØ§Ø±Ø´ÛŒ", + "app.poll.activePollInstruction": "برای مشاهده نتایج نظر سنجی ØŒ این پنجره را باز Ù†Ú¯Ù‡ دارید. در زمان مورد نظر میتوانید با کلیک بر روی دکمه انتشار نتایج... نتایج نظر سنجی را با کاربران خود به اشتراک بگذارید.", "app.poll.publishLabel": "انتشار نتایج نظرسنجی", - "app.poll.backLabel": "بازگشت به گزینه های نظرسنجی", + "app.poll.backLabel": "بازگشت به گزینه‌های نظرسنجی", "app.poll.closeLabel": "بستن", - "app.poll.waitingLabel": "در انتظار پاسخ ها ({0}/{1})", - "app.poll.ariaInputCount": "گزینه نظرسنجی شخصی {0} از {1}", + "app.poll.waitingLabel": "در انتظار پاسخ‌ها ({0}/{1})", + "app.poll.ariaInputCount": "گزینه نظرسنجی Ø³ÙØ§Ø±Ø´ÛŒ {0} از {1}", "app.poll.customPlaceholder": "Ø§ÙØ²ÙˆØ¯Ù† گزینه نظرسنجی", - "app.poll.noPresentationSelected": "هیچ ارائه ای انتخاب نشده است! Ù„Ø·ÙØ§ یکی را انتخاب کنید", + "app.poll.noPresentationSelected": "هیچ ارائه‌ای انتخاب نشده است! Ù„Ø·ÙØ§ یکی را انتخاب کنید.", "app.poll.clickHereToSelect": "برای انتخاب اینجا را کلیک Ú©Ù†", "app.poll.t": "درست", "app.poll.f": "نادرست", - "app.poll.tf": "درست/نادرست", + "app.poll.tf": "درست / نادرست", "app.poll.y": "بله", "app.poll.n": "خیر", - "app.poll.yn": "اری/نه", - "app.poll.a2": "A / B", - "app.poll.a3": "A / B / C", - "app.poll.a4": "A / B / C / D", - "app.poll.a5": "A / B / C / D", + "app.poll.yn": "بله / خیر", + "app.poll.a2": "ال٠/ ب", + "app.poll.a3": "ال٠/ ب / Ù¾", + "app.poll.a4": "ال٠/ ب / Ù¾ / ت", + "app.poll.a5": "ال٠/ ب / Ù¾ / ت", "app.poll.answer.true": "درست", "app.poll.answer.false": "نادرست", "app.poll.answer.yes": "بله", "app.poll.answer.no": "خیر", - "app.poll.answer.a": "A", - "app.poll.answer.b": "B", - "app.poll.answer.c": "C", - "app.poll.answer.d": "D", - "app.poll.answer.e": "E", + "app.poll.answer.a": "الÙ", + "app.poll.answer.b": "ب", + "app.poll.answer.c": "Ù¾", + "app.poll.answer.d": "ت", + "app.poll.answer.e": "Ø«", "app.poll.liveResult.usersTitle": "کاربران", "app.poll.liveResult.responsesTitle": "پاسخ", "app.polling.pollingTitle": "امکانات نظرسنجی", "app.polling.pollAnswerLabel": "پاسخ نظرسنجی {0}", "app.polling.pollAnswerDesc": "این گزینه را برای رای دادن به {0} انتخاب کنید.", "app.failedMessage": "عذر خواهی، مشکلی در اتصال به سرور به وجود آمده است.", - "app.downloadPresentationButton.label": "دانلود ÙØ§ÛŒÙ„ اصلی ارائه", + "app.downloadPresentationButton.label": "دانلود پرونده اصلی ارائه", "app.connectingMessage": "در ØØ§Ù„ اتصال ...", "app.waitingMessage": "ارتباط قطع شد. در ØØ§Ù„ تلاش مجدد در {0} ثانیه ...", "app.retryNow": "تلاش مجدد", - "app.navBar.settingsDropdown.optionsLabel": "گزینه ها", + "app.muteWarning.label": "برروی {0} کلیک کنید تا صدای خود را بی‌صدا کنید.", + "app.navBar.settingsDropdown.optionsLabel": "گزینه‌ها", "app.navBar.settingsDropdown.fullscreenLabel": "تمام ØµÙØÙ‡", "app.navBar.settingsDropdown.settingsLabel": "تنظیمات", "app.navBar.settingsDropdown.aboutLabel": "درباره", @@ -262,17 +277,17 @@ "app.navBar.toggleUserList.newMessages": "اعلان با پیام جدید", "app.navBar.recording": "جلسه در ØØ§Ù„ ضبط شدن است", "app.navBar.recording.on": "در ØØ§Ù„ ضبط", - "app.navBar.recording.off": "ضبط نمیشود", + "app.navBar.recording.off": "ضبط نمی‌شود", "app.navBar.emptyAudioBrdige": "میکروÙون ÙØ¹Ø§Ù„ÛŒ پیدا نشد.میکروÙÙ† خود را به اشتراک بگذارید تا قادر به ضبط باشد", "app.leaveConfirmation.confirmLabel": "ترک", "app.leaveConfirmation.confirmDesc": "شما را از جلسه خارج میکند", "app.endMeeting.title": "اتمام جلسه", - "app.endMeeting.description": "آیا مطمئنید Ú©Ù‡ میخواهید این جلسه را به اتمام برسانید؟", + "app.endMeeting.description": "آیا مطمئن هستید Ú©Ù‡ Ù…ÛŒ خواهید این جلسه را به اتمام برسانید ØŸ ( تمامی کاربران از جلسه خارج Ù…ÛŒ شوند)", "app.endMeeting.yesLabel": "بله", "app.endMeeting.noLabel": "خیر", "app.about.title": "درباره", "app.about.version": "نسخه کاربر:", - "app.about.copyright": "ØÙ‚ نشر", + "app.about.copyright": "ØÙ‚ نشر:", "app.about.confirmLabel": "تایید", "app.about.confirmDesc": "تایید", "app.about.dismissLabel": "لغو", @@ -288,10 +303,6 @@ "app.screenshare.screenShareLabel" : "اشتراک ØµÙØÙ‡", "app.submenu.application.applicationSectionTitle": "برنامه", "app.submenu.application.animationsLabel": "انیمیشن ها", - "app.submenu.application.audioAlertLabel": "اعلان های صوتی برای Ú¯ÙØªÚ¯Ùˆ", - "app.submenu.application.pushAlertLabel": "اعلان های پاپ اپ برای Ú¯ÙØªÚ¯Ùˆ", - "app.submenu.application.userJoinAudioAlertLabel": "هشدار صوتی برای کاربر وارد شده", - "app.submenu.application.userJoinPushAlertLabel": "پنجره هشدار ورود کاربر جدید", "app.submenu.application.fontSizeControlLabel": "اندازه متن", "app.submenu.application.increaseFontBtnLabel": "Ø§ÙØ²Ø§ÛŒØ´ اندازه متن برنامه", "app.submenu.application.decreaseFontBtnLabel": "کاهش اندازه متن برنامه", @@ -299,18 +310,24 @@ "app.submenu.application.languageLabel": "زبان برنامه", "app.submenu.application.languageOptionLabel": "انتخاب زبان", "app.submenu.application.noLocaleOptionLabel": "هیچ زبان ÙØ¹Ø§Ù„ÛŒ وجود ندارد", + "app.submenu.notification.SectionTitle": "اطلاعیه", + "app.submenu.notification.Desc": "چگونگی Ùˆ Ú†Ù‡ چیزی به شما اطلاع داده شود را تعری٠کنید.", + "app.submenu.notification.audioAlertLabel": "هشدارهای صوتی", + "app.submenu.notification.pushAlertLabel": "هشدارهای پاپ‌آپ", + "app.submenu.notification.messagesLabel": "پیام Ú¯ÙØªÚ¯Ùˆ", + "app.submenu.notification.userJoinLabel": "پیوستن کاربر", "app.submenu.audio.micSourceLabel": "ورودی صدای میکروÙÙ†", "app.submenu.audio.speakerSourceLabel": "ورودی صدای بلندگو", "app.submenu.audio.streamVolumeLabel": "بلندی صدای میکروÙÙ† شما", - "app.submenu.video.title": "ویدئو", - "app.submenu.video.videoSourceLabel": "منبع تصویر", + "app.submenu.video.title": "ویدیو", + "app.submenu.video.videoSourceLabel": "مشاهده منبع", "app.submenu.video.videoOptionLabel": "انتخاب منبع تصویر", - "app.submenu.video.videoQualityLabel": "Ú©ÛŒÙیت ویدئو", - "app.submenu.video.qualityOptionLabel": "انتخاب Ú©ÛŒÙیت تصویر", + "app.submenu.video.videoQualityLabel": "Ú©ÛŒÙیت ویدیو", + "app.submenu.video.qualityOptionLabel": "Ú©ÛŒÙیت ویدیو را انتخاب کنید", "app.submenu.video.participantsCamLabel": "مشاهده دوربین شرکت کنندگان", - "app.settings.applicationTab.label": "نرم Ø§ÙØ²Ø§Ø±", + "app.settings.applicationTab.label": "Ù†Ø±Ù…â€ŒØ§ÙØ²Ø§Ø±", "app.settings.audioTab.label": "صدا", - "app.settings.videoTab.label": "ویدئو", + "app.settings.videoTab.label": "ویدیو", "app.settings.usersTab.label": "شرکت کنندگان", "app.settings.main.label": "تنظیمات", "app.settings.main.cancel.label": "لغو", @@ -322,35 +339,39 @@ "app.settings.dataSavingTab.screenShare": "ÙØ¹Ø§Ù„ سازی اشتراک گذاری ØµÙØÙ‡", "app.settings.dataSavingTab.description": "برای صرÙÙ‡ جویی در مصر٠پهنای باند اینترنت آیتم هایی Ú©Ù‡ باید نمایش داده شوند را انتخاب کنید.", "app.settings.save-notification.label": "تنظیمات ذخیره شدند", + "app.statusNotifier.lowerHands": "دست‌های پایین", + "app.statusNotifier.raisedHandsTitle": "دست‌های بالا برده شده", + "app.statusNotifier.raisedHandDesc": "{0} دست خود را بالا برد", + "app.statusNotifier.and": "Ùˆ", "app.switch.onLabel": "روشن", "app.switch.offLabel": "خاموش", "app.talkingIndicator.ariaMuteDesc" : "برای بی صدا کردن کاربر انتخاب کنید", "app.talkingIndicator.isTalking" : "{0} در ØØ§Ù„ ØµØØ¨Øª کردن است", "app.talkingIndicator.wasTalking" : "{0} به ØµØØ¨Øª خود پایان داد", "app.actionsBar.actionsDropdown.actionsLabel": "ÙØ¹Ø§Ù„یت ها", - "app.actionsBar.actionsDropdown.presentationLabel": "بارگزاری ÙØ§ÛŒÙ„ ارائه", + "app.actionsBar.actionsDropdown.presentationLabel": "بارگذاری پرونده ارائه", "app.actionsBar.actionsDropdown.initPollLabel": "ایجاد نظرسنجی", "app.actionsBar.actionsDropdown.desktopShareLabel": "اشتراک ØµÙØÙ‡ نمایش", "app.actionsBar.actionsDropdown.lockedDesktopShareLabel": "اشتراک گذاری Ù‚ÙÙ„ شد", "app.actionsBar.actionsDropdown.stopDesktopShareLabel": "متوق٠کردن اشتراک گذاری میز کار", - "app.actionsBar.actionsDropdown.presentationDesc": "آپلود ÙØ§ÛŒÙ„ ارائه شما", + "app.actionsBar.actionsDropdown.presentationDesc": "بارگذاری پرونده ارائه شما", "app.actionsBar.actionsDropdown.initPollDesc": "ایجاد نظرسنجی", "app.actionsBar.actionsDropdown.desktopShareDesc": "اشتراک گذاری تصویر میز کار با دیگران", "app.actionsBar.actionsDropdown.stopDesktopShareDesc": "توق٠اشتراک گذاری تصویر میز کار با دیگران", "app.actionsBar.actionsDropdown.pollBtnLabel": "آغاز یک نظرسنجی", "app.actionsBar.actionsDropdown.pollBtnDesc": "تغییر وضعیت نمایش کادر نظرسنجی", - "app.actionsBar.actionsDropdown.saveUserNames": "ذخیره نام های کاربری", + "app.actionsBar.actionsDropdown.saveUserNames": "ذخیره نام‌های کاربری", "app.actionsBar.actionsDropdown.createBreakoutRoom": "ایجاد اتاق زیرمجموعه جلسه اصلی", - "app.actionsBar.actionsDropdown.createBreakoutRoomDesc": "ایجاد اتاق های زیرمجموعه به منظور چند تکه کردن جلسه کنونی", + "app.actionsBar.actionsDropdown.createBreakoutRoomDesc": "ایجاد اتاق‌های زیرمجموعه به منظور چند تکه کردن جلسه کنونی", "app.actionsBar.actionsDropdown.captionsLabel": "نوشتن زیرنویس", "app.actionsBar.actionsDropdown.captionsDesc": "تغییر وضعیت نمایش منوی عناویر", "app.actionsBar.actionsDropdown.takePresenter": "Ú¯Ø±ÙØªÙ† نقش ارائه دهنده", "app.actionsBar.actionsDropdown.takePresenterDesc": "تغییر نقش خود به ارائه دهنده", - "app.actionsBar.emojiMenu.statusTriggerLabel": "اعمال وضعیت", + "app.actionsBar.emojiMenu.statusTriggerLabel": "تنظیم وضعیت", "app.actionsBar.emojiMenu.awayLabel": "عدم ØØ¶ÙˆØ± در جلسه", "app.actionsBar.emojiMenu.awayDesc": "تغییر وضعیت خود به عدم ØØ¶ÙˆØ± در جلسه", "app.actionsBar.emojiMenu.raiseHandLabel": "اجازه Ú¯Ø±ÙØªÙ† از استاد", - "app.actionsBar.emojiMenu.raiseHandDesc": "دست خود را بالا برده تا از استاد درخواست سوال کنید", + "app.actionsBar.emojiMenu.raiseHandDesc": "دست خود را بالا برده تا از استاد سوالی بپرسید", "app.actionsBar.emojiMenu.neutralLabel": "تصمیم گیری نشده", "app.actionsBar.emojiMenu.neutralDesc": "تغییر وضعیت خود به تصمیم گیری نشده", "app.actionsBar.emojiMenu.confusedLabel": "گیج شدم", @@ -370,18 +391,18 @@ "app.actionsBar.currentStatusDesc": "وضعیت کنونی {0}", "app.actionsBar.captions.start": "آغار مشاهده زیرنویس ها", "app.actionsBar.captions.stop": "توق٠مشاهده زیرنویس ها", - "app.audioNotification.audioFailedError1001": "ارتباط سوکت وب قطع شد (خطای 1001)", - "app.audioNotification.audioFailedError1002": "امکان برقراری سوکت وب وجود ندارد (خطای 1002)", - "app.audioNotification.audioFailedError1003": "نسخه مرورگر پشتیبانی نمی شود (خطای 1003)", - "app.audioNotification.audioFailedError1004": "خطا در تماس (دلیل = {0}) (خطای 1004)", - "app.audioNotification.audioFailedError1005": "تماس به دلیل نامشخص قطع شد (خطای 1005)", - "app.audioNotification.audioFailedError1006": "تماس تایم اوت شد (خطای 1006)", - "app.audioNotification.audioFailedError1007": "خطا در اتصال (خطای ICE 1007)", - "app.audioNotification.audioFailedError1008": "انتقال با خطا مواجه شد (خطای 1008)", - "app.audioNotification.audioFailedError1009": "امکان Ø¯Ø±ÛŒØ§ÙØª اطلاعات سرور STUN/TURN وجود ندارد (خطای 1009)", - "app.audioNotification.audioFailedError1010": "تایم اون در اتصال اولیه (خطای 1010)", - "app.audioNotification.audioFailedError1011": "تایم اوت در اتصال (خطای ICE 1011)", - "app.audioNotification.audioFailedError1012": "اتصال بسته شد (خطای ICE 1012)", + "app.audioNotification.audioFailedError1001": "ارتباط وب‌سوکت قطع شد (خطای Û±Û°Û°Û±)", + "app.audioNotification.audioFailedError1002": "امکان برقراری وب‌سوکت وجود ندارد (خطای Û±Û°Û°Û²)", + "app.audioNotification.audioFailedError1003": "نسخه مرورگر پشتیبانی نمی‌شود (خطای Û±Û°Û°Û³)", + "app.audioNotification.audioFailedError1004": "خطا در تماس (دلیل = {0}) (خطای Û±Û°Û°Û´)", + "app.audioNotification.audioFailedError1005": "تماس به دلیل نامشخص قطع شد (خطای Û±Û°Û°Ûµ)", + "app.audioNotification.audioFailedError1006": "زمان تماس پایان ÛŒØ§ÙØª (خطای Û±Û°Û°Û¶)", + "app.audioNotification.audioFailedError1007": "خطا در اتصال (خطای ICE Û±Û°Û°Û·)", + "app.audioNotification.audioFailedError1008": "انتقال با خطا مواجه شد (خطای Û±Û°Û°Û¸)", + "app.audioNotification.audioFailedError1009": "امکان Ø¯Ø±ÛŒØ§ÙØª اطلاعات سرور STUN/TURN وجود ندارد (خطای Û±Û°Û°Û¹)", + "app.audioNotification.audioFailedError1010": "زمان در اتصال اولیه پایان ÛŒØ§ÙØª (خطای Û±Û°Û±Û°)", + "app.audioNotification.audioFailedError1011": "زمان در اتصال پایان ÛŒØ§ÙØª (خطای ICE Û±Û°Û±Û±)", + "app.audioNotification.audioFailedError1012": "اتصال بسته شد (خطای ICE Û±Û°Û±Û²)", "app.audioNotification.audioFailedMessage": "اتصال صدای شما با خطا موجه شد", "app.audioNotification.mediaFailedMessage": "متد getUserMicMedia به دلیل اینکه اتصال تنها از طریق لینک امن امکان پذیر است، با خطا مواجه شد", "app.audioNotification.closeLabel": "بستن", @@ -400,8 +421,8 @@ "app.audioModal.listenOnlyLabel": "تنها شنونده", "app.audioModal.audioChoiceLabel": "چنانچه مایل هستید تا از امکانات صوتی ذیل Ø§Ø³ØªÙØ§Ø¯Ù‡ کنید، روی آن کلیک نمایید.", "app.audioModal.iOSBrowser": "صدا/تصویر پیشتیبانی نمیشود", - "app.audioModal.iOSErrorDescription": "در ØØ§Ù„ ØØ§Ø¶Ø± صدا Ùˆ تصویر در مرورگر کروم iOS پشتیبانی نمیشود", - "app.audioModal.iOSErrorRecommendation": "پیشنهاد ما Ø§Ø³ØªÙØ§Ø¯Ù‡ از Ø³Ø§ÙØ§Ø±ÛŒ در IOS است", + "app.audioModal.iOSErrorDescription": "در ØØ§Ù„ ØØ§Ø¶Ø± صدا Ùˆ تصویر در مرورگر کروم iOS پشتیبانی نمی‌شود.", + "app.audioModal.iOSErrorRecommendation": "پیشنهاد ما Ø§Ø³ØªÙØ§Ø¯Ù‡ از مرورگر Ø³Ø§ÙØ§Ø±ÛŒ در iOS است.", "app.audioModal.audioChoiceDesc": "Ù†ØÙˆÙ‡ اتصال به صدا در این جلسه را انتخاب کنید", "app.audioModal.unsupportedBrowserLabel": "به نظر میرسد شما از مرورگری Ø§Ø³ØªÙØ§Ø¯Ù‡ میکنید Ú©Ù‡ به صورت کامل پشتیبانی نمیشود. Ù„Ø·ÙØ§ برای پشتیبانی کامل از {0} یا {1} Ø§Ø³ØªÙØ§Ø¯Ù‡ کنید", "app.audioModal.closeLabel": "بستن", @@ -414,10 +435,10 @@ "app.audioModal.helpTitle": "مشکلی در سخت Ø§ÙØ²Ø§Ø± صوت Ùˆ تصویر شما وجود دارد", "app.audioModal.helpText": "آیا شما مجوز Ø§Ø³ØªÙØ§Ø¯Ù‡ از میکروÙون خود را داده اید؟ شایان ذکر است Ú©Ù‡ هنگام اتصال به بخش صدا یک کادر میبایست باز شود Ú©Ù‡ از شما مجوز دسترسی به بخش مدیا را درخواست کند، Ù„Ø·ÙØ§ آن را برای اتصال به بخش صدا قبول کنید. اگر مشکل این نیست، تنظیمات مجوزات میکروÙون در مرورگر خود را تغییر دهید.", "app.audioModal.help.noSSL": "مشکل ssl در این ØµÙØÙ‡ وجود دارد. Ù„Ø·ÙØ§ با مدیر سیستم تماس بگیرید.", - "app.audioModal.help.macNotAllowed": "سیستم Ù…Ú© بوک شما دسترسی به میکروÙون را Ù…ØØ¯ÙˆØ¯ کرده از مسیر زیر مشکل را Ø±ÙØ¹ کنید.\n System Preferences > Security & Privacy > Privacy > Microphone", + "app.audioModal.help.macNotAllowed": "سیستم Ù…Ú© بوک شما دسترسی به میکروÙون را Ù…ØØ¯ÙˆØ¯ کرده از مسیر زیر مشکل را Ø±ÙØ¹ کنید.  System Preferences > Security & Privacy > Privacy > Microphone", "app.audioModal.audioDialTitle": "پیوستن به بخش صوتی با Ø§Ø³ØªÙØ§Ø¯Ù‡ از تلÙÙ† خود", "app.audioDial.audioDialDescription": "شماره گیری", - "app.audioDial.audioDialConfrenceText": "Ùˆ عدد پین Ú©Ù†ÙØ±Ø§Ù†Ø³ را وارد کنید:", + "app.audioDial.audioDialConfrenceText": "Ùˆ شماره پین Ú©Ù†ÙØ±Ø§Ù†Ø³ را وارد کنید:", "app.audioModal.autoplayBlockedDesc": "ما به مجوز شما جهت پخش صدا نیاز داریم", "app.audioModal.playAudio": "پخش صدا", "app.audioModal.playAudio.arialabel" : "پخش صدا", @@ -455,13 +476,13 @@ "app.meeting.logout.permissionEjectReason": "خارج شده به دلیل نقض مجوزات", "app.meeting.logout.ejectedFromMeeting": "شما از جلسه ØØ°Ù شدید", "app.meeting.logout.validateTokenFailedEjectReason": "خطا در اعتبارسنجی توکن Ø§ØØ±Ø§Ø² هویت", - "app.meeting.logout.userInactivityEjectReason": "کاربر برای مدت طولانی غیر ÙØ¹Ø§Ù„ است", - "app.meeting-ended.rating.legendLabel": "رتبه بندی بازخورد", + "app.meeting.logout.userInactivityEjectReason": "کاربر برای مدت طولانی ØºÛŒØ±ÙØ¹Ø§Ù„ است", + "app.meeting-ended.rating.legendLabel": "رتبه‌بندی بازخورد", "app.meeting-ended.rating.starLabel": "ستاره", "app.modal.close": "بستن", "app.modal.close.description": "ØØ°Ù تغییرات Ùˆ بستن مدال", "app.modal.confirm": "انجام شد", - "app.modal.newTab": "(تب جدید باز میشود)", + "app.modal.newTab": "(زبانه جدید باز می‌شود)", "app.modal.confirm.description": "ذخیره تغییرات Ùˆ بستن مدال", "app.dropdown.close": "بستن", "app.error.400": "درخواست اشتباه", @@ -469,7 +490,7 @@ "app.error.403": "شما از جلسه ØØ°Ù شدید", "app.error.404": "پیدا نشد", "app.error.410": "جلسه پایان ÛŒØ§ÙØª", - "app.error.500": "اوپس، خطای پیش آمده است", + "app.error.500": "آخ، خطای پیش آمده است", "app.error.leaveLabel": "دوباره وارد شوید", "app.error.fallback.presentation.title": "یک خطا رخ داده است", "app.error.fallback.presentation.description": "این مورد لاگ شد. Ù„Ø·ÙØ§ ØµÙØÙ‡ را دوباره سازی کنید.", @@ -482,10 +503,12 @@ "app.userList.guest.allowAllGuests": "اجازه ورود به همه مهمانان", "app.userList.guest.allowEveryone": "اجازه ورود به همه", "app.userList.guest.denyEveryone": "رد همه", - "app.userList.guest.pendingUsers": "{0} کاربران در انتظار تایید", + "app.userList.guest.pendingUsers": "{0} کاربر در انتظار تایید", "app.userList.guest.pendingGuestUsers": "{0} کاربر مهمان در انتظار تایید", "app.userList.guest.pendingGuestAlert": "به جلسه ملØÙ‚ شد Ùˆ منتظر تایید شماست", "app.userList.guest.rememberChoice": "به یاد داشتن انتخاب", + "app.userList.guest.acceptLabel": "Ù¾Ø°ÛŒØ±ÙØªÙ†", + "app.userList.guest.denyLabel": "رد کردن", "app.user-info.title": "جستجوی دایرکتوری", "app.toast.breakoutRoomEnded": "اتاق زیرمجموعه بسته شد، Ù„Ø·ÙØ§ دوباره به بخش صدا وارد شوید.", "app.toast.chat.public": "پیام جدید در Ú¯ÙØªÚ¯ÙˆÛŒ عمومی", @@ -493,57 +516,71 @@ "app.toast.chat.system": "سیستم", "app.toast.clearedEmoji.label": "وضعیت اموجی ها پاک شد", "app.toast.setEmoji.label": "وضعیت اموجی ها به {0} تغییر داده شد", - "app.toast.meetingMuteOn.label": "صدای همه کاربران به ØØ§Ù„ت بی صدا تغییر کرد", - "app.toast.meetingMuteOff.label": "امکان بیصدا کردن جلسه غیر ÙØ¹Ø§Ù„ شد", + "app.toast.meetingMuteOn.label": "صدای همه کاربران به ØØ§Ù„ت بی‌صدا تغییر کرد", + "app.toast.meetingMuteOff.label": "امکان بی‌صدا کردن جلسه ØºÛŒØ±ÙØ¹Ø§Ù„ شد", "app.notification.recordingStart": "جلسه در ØØ§Ù„ ضبط شدن است", - "app.notification.recordingStop": "این جلسه ضبط نمیشود", - "app.notification.recordingPaused": "جلسه دیگر ضبط نمیشود", - "app.notification.recordingAriaLabel": "زمان رکورد شده", + "app.notification.recordingStop": "این جلسه ضبط نمی‌شود", + "app.notification.recordingPaused": "جلسه دیگر ضبط نمی‌شود", + "app.notification.recordingAriaLabel": "زمان ضبط شده", "app.notification.userJoinPushAlert": "{0} به کلاس پیوستند", + "app.submenu.notification.raiseHandLabel": "بالا بردن دست", "app.shortcut-help.title": "میانبرهای ØµÙØÙ‡ کلید", - "app.shortcut-help.accessKeyNotAvailable": "کلید های دسترسی موجود نیست", + "app.shortcut-help.accessKeyNotAvailable": "کلیدهای دسترسی موجود نیست", "app.shortcut-help.comboLabel": "ترکیبی", "app.shortcut-help.functionLabel": "تابع", "app.shortcut-help.closeLabel": "بستن", "app.shortcut-help.closeDesc": "بستن کادر میانبرهای ØµÙØÙ‡ کلید", - "app.shortcut-help.openOptions": "باز کردن گزینه ها", - "app.shortcut-help.toggleUserList": "تغییر وضعیت نمایش لیست دانش آموزان", + "app.shortcut-help.openOptions": "باز کردن گزینه‌ها", + "app.shortcut-help.toggleUserList": "تغییر وضعیت نمایش Ùهرست کاربران", "app.shortcut-help.toggleMute": "بی صدا / با صدا", - "app.shortcut-help.togglePublicChat": "تغییر وضعیت Ú¯ÙØªÚ¯ÙˆÛŒ عمومی (لیست کاربر باید باز باشد)", + "app.shortcut-help.togglePublicChat": "تغییر وضعیت Ú¯ÙØªÚ¯ÙˆÛŒ عمومی (Ùهرست کاربر باید باز باشد)", "app.shortcut-help.hidePrivateChat": "پنهان کردن Ú¯ÙØªÚ¯ÙˆÛŒ خصوصی", "app.shortcut-help.closePrivateChat": "بستن Ú¯ÙØªÚ¯ÙˆÛŒ خصوصی", - "app.shortcut-help.openActions": "باز کردن منوی ÙØ¹Ø§Ù„یت ها", + "app.shortcut-help.openActions": "باز کردن منوی ÙØ¹Ø§Ù„یت‌ها", "app.shortcut-help.openStatus": "باز کردن منوی وضعیت", "app.shortcut-help.togglePan": "ÙØ¹Ø§Ù„سازی ابزار Pan (ارائه دهنده)", "app.shortcut-help.nextSlideDesc": "اسلاید بعدی (ارائه دهنده)", "app.shortcut-help.previousSlideDesc": "اسلاید قبلی (ارائه دهنده)", "app.lock-viewers.title": "Ù‚ÙÙ„ کردن کاربران", - "app.lock-viewers.description": "این قابلیت شما را قادر میسازد تا دسترسی به امکانات ویژه را از کاربران بگیرید.", + "app.lock-viewers.description": "این قابلیت شما را قادر می‌سازد تا دسترسی به امکانات ویژه را از کاربران بگیرید.", "app.lock-viewers.featuresLable": "امکان", "app.lock-viewers.lockStatusLabel": "وضعیت", "app.lock-viewers.webcamLabel": "اشتراک گذاری دوربین", "app.lock-viewers.otherViewersWebcamLabel": "مشاهده دوربین سایر کاربران", "app.lock-viewers.microphoneLable": "اشتراک گذاری میکروÙون", - "app.lock-viewers.PublicChatLabel": "ارسال پیام گروهی", + "app.lock-viewers.PublicChatLabel": "ارسال پیام عمومی", "app.lock-viewers.PrivateChatLable": "ارسال پیام خصوصی", "app.lock-viewers.notesLabel": "ویرایش یاداشت عمومی", - "app.lock-viewers.userListLabel": "مشاهده بقیه کاربران", + "app.lock-viewers.userListLabel": "مشاهده بقیه کاربران در Ùهرست کاربران", "app.lock-viewers.ariaTitle": "نمایش به صورت درجا", "app.lock-viewers.button.apply": "اعمال", "app.lock-viewers.button.cancel": "لغو", "app.lock-viewers.locked": "Ù‚ÙÙ„ شده", "app.lock-viewers.unlocked": "Ø±ÙØ¹ بلاک", + "app.connection-status.ariaTitle": "Ú©ÛŒÙیت وضعیت اتصال", + "app.connection-status.title": "وضعیت اتصال", + "app.connection-status.description": "وضعیت اتصال کاربران را مشاهده کنید", + "app.connection-status.empty": "تاکنون هیچ مشکلی در هنگام اتصال گزارش نشده است", + "app.connection-status.more": "بیشتر", + "app.connection-status.offline": "Ø¢Ùلاین", "app.recording.startTitle": "شروع ضبط", - "app.recording.stopTitle": "متوق٠کردن ضبط", + "app.recording.stopTitle": "Ù…Ú©Ø« ضبط", "app.recording.resumeTitle": "از سر Ú¯Ø±ÙØªÙ† ضبط", - "app.recording.startDescription": "شما میتوانید برای توق٠ضبط ØŒ مجددا دکمه ضبط را ÙØ´Ø§Ø± دهید.", - "app.recording.stopDescription": "آیا از توق٠ضبط اطمینان دارید؟شما میتوانید مجددا عملیات ضبط را ÙØ¹Ø§Ù„ نمایید", + "app.recording.startDescription": "شما بعد از زدن دکمه ضبط، می‌توانید در هنگام ضبط آن را Ù…Ú©Ø« کنید.", + "app.recording.stopDescription": "آیا از Ù…Ú©Ø« ضبط اطمینان دارید؟ شما با زدن دکمه ضبط مجددا می‌توانید، ضبط را ادامه بدهید.", "app.videoPreview.cameraLabel": "دوربین", "app.videoPreview.profileLabel": "Ú©ÛŒÙیت", + "app.videoPreview.quality.low": "Ú©Ù…", + "app.videoPreview.quality.medium": "متوسط", + "app.videoPreview.quality.high": "بالا", + "app.videoPreview.quality.hd": "Ú©ÛŒÙیت بالا", "app.videoPreview.cancelLabel": "لغو", "app.videoPreview.closeLabel": "بستن", "app.videoPreview.findingWebcamsLabel": "جستجوی وب Ú©Ù…", "app.videoPreview.startSharingLabel": "آغاز اشتراک گذاری", + "app.videoPreview.stopSharingLabel": "متوق٠کردن اشتراک‌گذاری", + "app.videoPreview.stopSharingAllLabel": "متوق٠کردن همه", + "app.videoPreview.sharedCameraLabel": "این دوربین از قبل به اشتراک گذاشته شده است", "app.videoPreview.webcamOptionLabel": "انتخاب دوربین", "app.videoPreview.webcamPreviewLabel": "پیش نمایش دوربین", "app.videoPreview.webcamSettingsTitle": "تنظیمات دوربین", @@ -552,7 +589,7 @@ "app.video.joinVideo": "اشتراک گذاری دوربین", "app.video.leaveVideo": "متوق٠کردن اشتراک گذاری دوربین", "app.video.iceCandidateError": "خطا در Ø§ÙØ²ÙˆØ¯Ù† کاندید ICE", - "app.video.iceConnectionStateError": "خطا در اتصال (خطای ICE 1107)", + "app.video.iceConnectionStateError": "اتصال موÙقیت آمیز نبود (خطای ICE Û±Û±Û°Û·)", "app.video.permissionError": "خطا در اشتراک گذاری دوربین. Ù„Ø·ÙØ§ مجوزات دسترسی را بررسی کنید", "app.video.sharingError": "خطا در اشتراک دوربین", "app.video.notFoundError": "دوربین ÛŒØ§ÙØª نشد. Ù„Ø·ÙØ§ مطمئن شوید Ú©Ù‡ دوربین وصل است", @@ -560,32 +597,21 @@ "app.video.notSupportedError": "تنها امکان اشتراک ویدئو از منابع امن امکان پذیر است، مطمئن شوید گواهی SSL تان معتبر است", "app.video.notReadableError": "عدم امکان دسترسی به دوربین، مطمئن شوید دوربین شما توسط برنامه دیگری در ØØ§Ù„ Ø§Ø³ØªÙØ§Ø¯Ù‡ نیست", "app.video.mediaFlowTimeout1020": "امکان Ø¯Ø±ÛŒØ§ÙØª مدیا از سرور وجود ندارد (خطای 1020)", - "app.video.suggestWebcamLock": "آیا تنظیمات Ù‚ÙÙ„ به دوربین های کاربران اعمال شود؟", - "app.video.suggestWebcamLockReason": "(این مورد باعث بهبود Ú©ÛŒÙیت جلسه خواهد شد)", + "app.video.suggestWebcamLock": "آیا تنظیمات Ù‚ÙÙ„ برای وب‌کم‌های کاربران اعمال شود؟", + "app.video.suggestWebcamLockReason": "«این مورد باعث بهبود Ú©ÛŒÙیت جلسه خواهد شد»", "app.video.enable": "ÙØ¹Ø§Ù„", "app.video.cancel": "لغو", "app.video.swapCam": "جابجا کردن", - "app.video.swapCamDesc": "جابجا کردن جهت تصویر دوربین ها", - "app.video.videoLocked": "اشتراک گذاری دوبین Ù‚ÙÙ„ شده است", - "app.video.videoButtonDesc": "اشتراک گذاری دوربین", - "app.video.videoMenu": "منوی دوربین", - "app.video.videoMenuDisabled": "منوی دوربین در تنظیمات ØºÛŒØ±ÙØ¹Ø§Ù„ شده است", - "app.video.videoMenuDesc": "باز کردن منوی تنظیمات دوربین", + "app.video.swapCamDesc": "جابجا کردن جهت تصویر وب‌کم‌ها", + "app.video.videoLocked": "اشتراک‌گذاری وب‌کم Ù‚ÙÙ„ شده است", + "app.video.videoButtonDesc": "اشتراک‌گذاری وب‌کم", + "app.video.videoMenu": "منوی ویدیو", + "app.video.videoMenuDisabled": "منوی ویدیوی وب‌کم در تنظیمات ØºÛŒØ±ÙØ¹Ø§Ù„ شده است", + "app.video.videoMenuDesc": "باز کردن منوی کشویی ویدیو", "app.video.chromeExtensionError": "شما باید نصب کنید", "app.video.chromeExtensionErrorLink": "این Ø§ÙØ²ÙˆÙ†Ù‡ کروم", - "app.video.stats.title": "آمار اتصال", - "app.video.stats.packetsReceived": "بسته های Ø¯Ø±ÛŒØ§ÙØª شده", - "app.video.stats.packetsSent": "بسته های ارسال شده", - "app.video.stats.packetsLost": "بسته های از دست Ø±ÙØªÙ‡", - "app.video.stats.bitrate": "بیت ریت", - "app.video.stats.lostPercentage": "درصد از دست Ø±ÙØªÙ‡", - "app.video.stats.lostRecentPercentage": "درصد از دست Ø±ÙØªÙ‡ اخیر", - "app.video.stats.dimensions": "ابعاد", - "app.video.stats.codec": "کدک", - "app.video.stats.decodeDelay": "تاخیر بازگشایی", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "میزان Ø§Ø³ØªÙØ§Ø¯Ù‡ از انکود", - "app.video.stats.currentDelay": "تاخیر جاری", + "app.video.pagination.prevPage": "مشاهده ویدیوهای قبلی", + "app.video.pagination.nextPage": "مشاهده ویدیوهای بعدی", "app.fullscreenButton.label": "تغییر {0} به تمام ØµÙØÙ‡", "app.deskshare.iceConnectionStateError": "ارتباط هنگام اشتراک ØµÙØ®Ù‡ با خطا مواجه شد (خطای ICE 1108)", "app.sfu.mediaServerConnectionError2000": "امکان اتصال به مدیا سرور وجود ندارد (خطای 2000)", @@ -598,7 +624,8 @@ "app.sfu.invalidSdp2202":"کاربر یک درخواست مدیای نامعتبر تولید کرده است (خطای SDP 2202)", "app.sfu.noAvailableCodec2203": "سرور امکان ÛŒØ§ÙØªÙ† یک کدک مناسب را ندارد (خطای 2203)", "app.meeting.endNotification.ok.label": "تایید", - "app.whiteboard.annotations.poll": "نتایج نظرسنجی منتشر شده است", + "app.whiteboard.annotations.poll": "نتایج نظرسنجی منتشر شد", + "app.whiteboard.annotations.pollResult": "نتیجه نظرسنجی", "app.whiteboard.toolbar.tools": "ابزارها", "app.whiteboard.toolbar.tools.hand": "پن", "app.whiteboard.toolbar.tools.pencil": "مداد", @@ -609,8 +636,8 @@ "app.whiteboard.toolbar.tools.text": "متن", "app.whiteboard.toolbar.thickness": "ضخامت نوشته", "app.whiteboard.toolbar.thicknessDisabled": "ضخیم سازی نوشته غیر ÙØ¹Ø§Ù„ شده است", - "app.whiteboard.toolbar.color": "رنگ ها", - "app.whiteboard.toolbar.colorDisabled": "رنگ ها ØºÛŒØ±ÙØ¹Ø§Ù„ شده است", + "app.whiteboard.toolbar.color": "رنگ‌ها", + "app.whiteboard.toolbar.colorDisabled": "رنگ‌ها ØºÛŒØ±ÙØ¹Ø§Ù„ شده است", "app.whiteboard.toolbar.color.black": "مشکی", "app.whiteboard.toolbar.color.white": "سÙید", "app.whiteboard.toolbar.color.red": "قرمز", @@ -621,8 +648,8 @@ "app.whiteboard.toolbar.color.dodgerBlue": "آبی تیره", "app.whiteboard.toolbar.color.blue": "آبی", "app.whiteboard.toolbar.color.violet": "Ø¨Ù†ÙØ´", - "app.whiteboard.toolbar.color.magenta": "قرمز", - "app.whiteboard.toolbar.color.silver": "نقره ای", + "app.whiteboard.toolbar.color.magenta": "ارغوانی روشن", + "app.whiteboard.toolbar.color.silver": "نقره‌ای", "app.whiteboard.toolbar.undo": "لغو ØØ§Ø´ÛŒÙ‡ نویسی", "app.whiteboard.toolbar.clear": "پاک کردن همه ØØ§Ø´ÛŒÙ‡ نویسی ها", "app.whiteboard.toolbar.multiUserOn": "ÙØ¹Ø§Ù„ کردن ØØ§Ù„ت چند کابره تخته سیاه", @@ -630,7 +657,7 @@ "app.whiteboard.toolbar.fontSize": "لیست اندازه قلم", "app.feedback.title": "شما از Ú©Ù†ÙØ±Ø§Ù†Ø³ خارج شده اید", "app.feedback.subtitle": "بسیار ممنون میشویم نظر خود را در خصوص Ø§Ø³ØªÙØ§Ø¯Ù‡ از برنامه بیگ بلو باتن Ø¨ÙØ±Ù…ایید (اختیاری)", - "app.feedback.textarea": "چگونه میتوانیم برنامه بیگ بلو باتن را بهبود دهیم؟", + "app.feedback.textarea": "چگونه می‌توانیم برنامه بیگ بلو باتن را بهبود دهیم؟", "app.feedback.sendFeedback": "ارسال بازخورد", "app.feedback.sendFeedbackDesc": "ارسال بازخورد Ùˆ ترک جلسه", "app.videoDock.webcamFocusLabel": "تمرکز", @@ -641,12 +668,12 @@ "app.videoDock.autoplayAllowLabel": "مشاهده دوربین ها", "app.invitation.title": "دعوت به اتاق زیرمجموعه", "app.invitation.confirm": "دعوت", - "app.createBreakoutRoom.title": "اتاق های زیرمجموعه", - "app.createBreakoutRoom.ariaTitle": "پنهان سازی اتاق های زیرمجموعه", - "app.createBreakoutRoom.breakoutRoomLabel": "اتاق های زیرمجموعه {0}", - "app.createBreakoutRoom.generatingURL": "در ØØ§Ù„ تولید ادرس", - "app.createBreakoutRoom.generatedURL": "تولید شده", - "app.createBreakoutRoom.duration": "طول {0}", + "app.createBreakoutRoom.title": "اتاق‌های زیرمجموعه", + "app.createBreakoutRoom.ariaTitle": "پنهان کردن اتاق‌های زیرمجموعه", + "app.createBreakoutRoom.breakoutRoomLabel": "اتاق‌های زیرمجموعه {0}", + "app.createBreakoutRoom.generatingURL": "در ØØ§Ù„ تولید نشانی وب", + "app.createBreakoutRoom.generatedURL": "تولید شد", + "app.createBreakoutRoom.duration": "مدت زمان {0}", "app.createBreakoutRoom.room": "اتاق {0}", "app.createBreakoutRoom.notAssigned": "واگذار نشده ({0})", "app.createBreakoutRoom.join": "پیوستن به اتاق", @@ -655,13 +682,13 @@ "app.createBreakoutRoom.alreadyConnected": "ØØ§Ø¶Ø± در جلسه", "app.createBreakoutRoom.confirm": "ایجاد کردن", "app.createBreakoutRoom.record": "ضبط کردن", - "app.createBreakoutRoom.numberOfRooms": "تعداد اتاق ها", - "app.createBreakoutRoom.durationInMinutes": "زمان (دقیقه)", + "app.createBreakoutRoom.numberOfRooms": "تعداد اتاق‌ها", + "app.createBreakoutRoom.durationInMinutes": "مدت زمان (دقیقه)", "app.createBreakoutRoom.randomlyAssign": "به صورت تصادÙÛŒ واگذار شده", - "app.createBreakoutRoom.endAllBreakouts": "پایان تمام اتاق های زیرمجموعه", - "app.createBreakoutRoom.roomName": "{0} (اتاق -{1})", + "app.createBreakoutRoom.endAllBreakouts": "پایان تمام اتاق‌های زیرمجموعه", + "app.createBreakoutRoom.roomName": "{0} (اتاق - {1})", "app.createBreakoutRoom.doneLabel": "انجام شد", - "app.createBreakoutRoom.nextLabel": "بعد", + "app.createBreakoutRoom.nextLabel": "بعدی", "app.createBreakoutRoom.minusRoomTime": "کاهش زمان اتاق زیرمجموعه به", "app.createBreakoutRoom.addRoomTime": "Ø§ÙØ²Ø§ÛŒØ´ زمان اتاق زیرمجموعه به ", "app.createBreakoutRoom.addParticipantLabel": "+ Ø§ÙØ²ÙˆØ¯Ù† شرکت کننده", @@ -679,13 +706,13 @@ "app.externalVideo.autoPlayWarning": "برای به هنگام سازی ØŒ ویدیو را پخش کنید", "app.network.connection.effective.slow": "ما مشکلاتی در اتصال مشاهده میکنیم", "app.network.connection.effective.slow.help": "اطلاعات بیشتر", - "app.externalVideo.noteLabel": "نکته: ویدئوهای به اشتراک گذاشته شده در بازپخش ضبط نمایش داده نخواهند شد\n ", + "app.externalVideo.noteLabel": "نکته: ویدیوهای خارجی به اشتراک گذاشته شده در ضبط ظاهر نمی‌شوند.نشانی‌های وب یوتیوب، ویمیو، Instructure MediaØŒ توییچ، دیلی‌موشن Ùˆ ÙØ§ÛŒÙ„‌های رسانه‌ای (به عنوان مثال https://example.com/xy.mp4) پشتیبانی می‌شوند.", "app.actionsBar.actionsDropdown.shareExternalVideo": "اشتراک یک ویدیوی خارجی", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "متوق٠کردن نمایش ویدیوی خارجی", - "app.iOSWarning.label": "Ù„Ø·ÙØ§ به IOS نسخه 12.2 یا بالاتر ارتقا دهید", - "app.legacy.unsupportedBrowser": "به نظر میرسد شما از مرورگری Ø§Ø³ØªÙØ§Ø¯Ù‡ میکنید Ú©Ù‡ پشتیبانی نمیشود. Ù„Ø·ÙØ§ برای پشتیبانی کامل از {0} یا {1} Ø§Ø³ØªÙØ§Ø¯Ù‡ کنید", - "app.legacy.upgradeBrowser": "به نظر میرسد شما از نسخه قدیمی یک مرورگر پشتیبانی شده Ø§Ø³ØªÙØ§Ø¯Ù‡ میکنید. Ù„Ø·ÙØ§ به منظور پشتیبانی کامل مرورگر خود را به روز رسانی کنید.", - "app.legacy.criosBrowser": "در iOS برای پشتیبانی کامل Ù„Ø·ÙØ§ از Ø³Ø§ÙØ§Ø±ÛŒ Ø§Ø³ØªÙØ§Ø¯Ù‡ کنید" + "app.iOSWarning.label": "Ù„Ø·ÙØ§ به iOS نسخه 12.2 یا بالاتر ارتقا دهید", + "app.legacy.unsupportedBrowser": "به نظر می‌رسد شما از مرورگری Ø§Ø³ØªÙØ§Ø¯Ù‡ می‌کنید Ú©Ù‡ پشتیبانی نمی‌شود. Ù„Ø·ÙØ§ برای پشتیبانی کامل از {0} یا {1} Ø§Ø³ØªÙØ§Ø¯Ù‡ کنید.", + "app.legacy.upgradeBrowser": "به نظر می‌رسد شما از نسخه قدیمی مرورگر پشتیبانی شده Ø§Ø³ØªÙØ§Ø¯Ù‡ می‌کنید. Ù„Ø·ÙØ§ به منظور پشتیبانی کامل مرورگر خود را به‌روزرسانی کنید.", + "app.legacy.criosBrowser": "در iOS برای پشتیبانی کامل Ù„Ø·ÙØ§ از Ø³Ø§ÙØ§Ø±ÛŒ Ø§Ø³ØªÙØ§Ø¯Ù‡ کنید." } diff --git a/bigbluebutton-html5/private/locales/fi.json b/bigbluebutton-html5/private/locales/fi.json index 2a7b80f831b3ef9ef8baa364717bd19efe91d69e..52e28d6784172b5f3707dcd199b8ade7410167ba 100644 --- a/bigbluebutton-html5/private/locales/fi.json +++ b/bigbluebutton-html5/private/locales/fi.json @@ -39,10 +39,10 @@ "app.note.title": "Jaetut muistiinpanot", "app.note.label": "Muistiinpano", "app.note.hideNoteLabel": "Piilota muistiinpano", + "app.note.tipLabel": "Paina ESC-näppäintä keskittääksesi editorin työkaluriviin", "app.user.activityCheck": "Käyttäjien aktiivisuuden tarkistus", "app.user.activityCheck.label": "tarkista onko käyttäjä vielä tapaamisessa ({0})", "app.user.activityCheck.check": "Tarkista", - "app.note.tipLabel": "Paina ESC-näppäintä keskittääksesi editorin työkaluriviin", "app.userList.usersTitle": "Käyttäjät", "app.userList.participantsTitle": "Osallistujat", "app.userList.messagesTitle": "Viestit", @@ -101,8 +101,6 @@ "app.meeting.meetingTimeRemaining": "Aikaa jäljellä tapaamisessa: {0}", "app.meeting.meetingTimeHasEnded": "Aika päättyi, tapaaminen suljetaan pian", "app.meeting.endedMessage": "Sinut ohjataan automaattisesti takaisin kotinäkymään", - "app.meeting.alertMeetingEndsUnderOneMinute": "Tapaaminen suljetaan minuutin sisällä", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Breakout-huone sulkeutuu minuutin sisällä", "app.presentation.hide": "Piilota esitys", "app.presentation.notificationLabel": "Nykyinen esitys", "app.presentation.slideContent": "Dian sisältö", @@ -229,7 +227,6 @@ "app.leaveConfirmation.confirmLabel": "Poistu", "app.leaveConfirmation.confirmDesc": "Kirjaa sinut ulos tapaamisesta.", "app.endMeeting.title": "Sulje tapaaminen", - "app.endMeeting.description": "Oletko varma että haluat sulkea tämän istunnon?", "app.endMeeting.yesLabel": "Kyllä", "app.endMeeting.noLabel": "Ei", "app.about.title": "Tietoja", @@ -250,8 +247,6 @@ "app.screenshare.screenShareLabel" : "Ruudunjako", "app.submenu.application.applicationSectionTitle": "Sovellus", "app.submenu.application.animationsLabel": "Animaatiot", - "app.submenu.application.audioAlertLabel": "Audiohälytykset chatille", - "app.submenu.application.pushAlertLabel": "Pop Up -hälytykset chatille", "app.submenu.application.fontSizeControlLabel": "Fontin koko", "app.submenu.application.increaseFontBtnLabel": "Lisää sovelluksen fonttikokoa", "app.submenu.application.decreaseFontBtnLabel": "Pienennä sovelluksen fonttikokoa", @@ -327,7 +322,7 @@ "app.audioNotification.audioFailedMessage": "Sinun audioyhteytesi ei pystynyt yhdistymään.", "app.audioNotification.mediaFailedMessage": "getUserMicMedia epäonnistui koska vain alkuperäiset lähteet ovat sallittu.", "app.audioNotification.closeLabel": "Sulje", - "app.audioNotificaion.reconnectingAsListenOnly": "Katselijoiden mikrofonit ovat lukittu esittäjän toimesta, sinut ohjataan automaattisesti \"vain kuuntelu\" -tilaan.", + "app.audioNotificaion.reconnectingAsListenOnly": "Katselijoiden mikrofonit ovat lukittu esittäjän toimesta, sinut ohjataan automaattisesti "vain kuuntelu" -tilaan.", "app.breakoutJoinConfirmation.title": "Liity Breakout-huoneeseen", "app.breakoutJoinConfirmation.message": "Haluatko liittyä?", "app.breakoutJoinConfirmation.confirmDesc": "Sinut liitetään breakout-huoneeseen", @@ -488,20 +483,8 @@ "app.video.videoMenuDesc": "Avaa video-alasvetovalikko", "app.video.chromeExtensionError": "Sinun täytyy asentaa", "app.video.chromeExtensionErrorLink": "tämä Chrome-selaimen lisäosa", - "app.video.stats.title": "Yhteyden statistiikka", - "app.video.stats.packetsReceived": "Pakettia vastaanotettu", - "app.video.stats.packetsSent": "Pakettia lähetetty", - "app.video.stats.packetsLost": "Pakettia kadonnut", - "app.video.stats.bitrate": "Bittinopeus", - "app.video.stats.lostPercentage": "Kokonaisprosentteja menetetty", - "app.video.stats.lostRecentPercentage": "Äskettäin menetetty prosentteina", - "app.video.stats.dimensions": "Mittasuhteet", - "app.video.stats.codec": "Koodekki", - "app.video.stats.decodeDelay": "Dekoodauksen viive", - "app.video.stats.currentDelay": "Nykyinen viive", "app.fullscreenButton.label": "Aseta {0} kokoruuduntilaan.", "app.meeting.endNotification.ok.label": "OK", - "app.whiteboard.annotations.poll": "Kyselyn tulokset julkistettiin", "app.whiteboard.toolbar.tools": "Työkalut", "app.whiteboard.toolbar.tools.hand": "Liikuta", "app.whiteboard.toolbar.tools.pencil": "Kynä", diff --git a/bigbluebutton-html5/private/locales/fr.json b/bigbluebutton-html5/private/locales/fr.json index 4b3555d1957ecc782d8df6ade51c574c8b2feaa5..426cb6e3379e5a01356e755d0f42ffbeeeaeb53d 100644 --- a/bigbluebutton-html5/private/locales/fr.json +++ b/bigbluebutton-html5/private/locales/fr.json @@ -1,23 +1,24 @@ { "app.home.greeting": "Votre présentation commencera dans quelques instants...", - "app.chat.submitLabel": "Envoyer message", - "app.chat.errorMaxMessageLength": "Le message est {0} caractère(s) trop long", + "app.chat.submitLabel": "Envoyer le message", + "app.chat.errorMaxMessageLength": "Le message est trop long de {0} caractère(s)", "app.chat.disconnected": "Vous êtes déconnecté, les messages ne peuvent pas être envoyés", - "app.chat.locked": "Le chat est verrouillé, les messages ne peuvent pas être envoyés", - "app.chat.inputLabel": "Saisie des messages pour le chat {0}", - "app.chat.inputPlaceholder": "Envoyer message à {0}", + "app.chat.locked": "La discussion est verrouillée, les messages ne peuvent pas être envoyés", + "app.chat.inputLabel": "Saisie des messages pour la discussion {0}", + "app.chat.inputPlaceholder": "Envoyer un message à {0}", "app.chat.titlePublic": "Discussion publique", "app.chat.titlePrivate": "Discussion privée avec {0}", "app.chat.partnerDisconnected": "{0} a quitté la conférence", "app.chat.closeChatLabel": "Fermer {0}", "app.chat.hideChatLabel": "Cacher {0}", "app.chat.moreMessages": "Plus de messages ci-dessous", - "app.chat.dropdown.options": "Options de Discussion", + "app.chat.dropdown.options": "Options de discussion", "app.chat.dropdown.clear": "Effacer", "app.chat.dropdown.copy": "Copier", "app.chat.dropdown.save": "Sauvegarder", "app.chat.label": "Discussion", "app.chat.offline": "Déconnecté", + "app.chat.pollResult": "Résultats du sondage", "app.chat.emptyLogLabel": "Journal de discussion vide", "app.chat.clearPublicChatMessage": "L'historique des discussions publiques a été effacé par un modérateur", "app.chat.multi.typing": "Plusieurs utilisateurs sont en train d'écrire", @@ -25,7 +26,7 @@ "app.chat.two.typing": "{0} et {1} sont en train d'écrire", "app.captions.label": "Sous-titre", "app.captions.menu.close": "Fermer", - "app.captions.menu.start": "Débuter", + "app.captions.menu.start": "Démarrer", "app.captions.menu.ariaStart": "Démarrer l'écriture des sous-titres", "app.captions.menu.ariaStartDesc": "Ouvre l'éditeur de sous-titres et ferme la fenêtre modale", "app.captions.menu.select": "Sélectionnez une langue disponible", @@ -41,19 +42,19 @@ "app.captions.pad.hide": "Cacher les sous-titres", "app.captions.pad.tip": "Appuyez sur Echap pour centrer sur la barre d'outils de l'éditeur.", "app.captions.pad.ownership": "Prendre le contrôle", - "app.captions.pad.ownershipTooltip": "Vous serez attribué en tant que propriétaire de {0} légendes.", + "app.captions.pad.ownershipTooltip": "Vous serez désigné propriétaire de {0} légendes.", "app.captions.pad.interimResult": "Résultats intermédiaires", - "app.captions.pad.dictationStart": "Démarrer la diction", - "app.captions.pad.dictationStop": "Arrêter la diction", - "app.captions.pad.dictationOnDesc": "Activer la reconnaissance orale", - "app.captions.pad.dictationOffDesc": "Désactiver la reconnaissance orale", - "app.note.title": "Notes Partagées", + "app.captions.pad.dictationStart": "Démarrer la dictée", + "app.captions.pad.dictationStop": "Arrêter la dictée", + "app.captions.pad.dictationOnDesc": "Activer la reconnaissance vocale", + "app.captions.pad.dictationOffDesc": "Désactiver la reconnaissance vocale", + "app.note.title": "Notes partagées", "app.note.label": "Note", "app.note.hideNoteLabel": "Masquer la note", + "app.note.tipLabel": "Appuyez sur Echap pour centrer sur la barre d'outils de l'éditeur", "app.user.activityCheck": "Vérification de l'activité de l'utilisateur", "app.user.activityCheck.label": "Vérifier si l'utilisateur est toujours en réunion ({0})", "app.user.activityCheck.check": "Vérifier", - "app.note.tipLabel": "Appuyez sur Echap pour centrer sur la barre d'outils de l'éditeur.", "app.userList.usersTitle": "Utilisateurs", "app.userList.participantsTitle": "Participants", "app.userList.messagesTitle": "Messages", @@ -63,8 +64,8 @@ "app.userList.presenter": "Présentateur", "app.userList.you": "Vous", "app.userList.locked": "Verrouillé", - "app.userList.byModerator": "par (Modérateur)", - "app.userList.label": "Liste d'utilisateur", + "app.userList.byModerator": "par (modérateur)", + "app.userList.label": "Liste d'utilisateurs", "app.userList.toggleCompactView.label": "Basculer le mode d'affichage compact", "app.userList.guest": "Invité", "app.userList.menuTitleContext": "Options disponibles", @@ -73,28 +74,30 @@ "app.userList.menu.chat.label": "Démarrer une discussion privée", "app.userList.menu.clearStatus.label": "Effacer le statut", "app.userList.menu.removeUser.label": "Retirer l'utilisateur", - "app.userList.menu.removeConfirmation.label": "Supprimer utilisateur ({0})", - "app.userlist.menu.removeConfirmation.desc": "Empêcher cet utilisateur de joindre de nouveau la session.", - "app.userList.menu.muteUserAudio.label": "Rendre Muet", - "app.userList.menu.unmuteUserAudio.label": "Autoriser à parler", + "app.userList.menu.removeConfirmation.label": "Supprimer l'utilisateur ({0})", + "app.userlist.menu.removeConfirmation.desc": "Empêcher cet utilisateur de rejoindre de nouveau la session.", + "app.userList.menu.muteUserAudio.label": "Rendre muet l'utilisateur", + "app.userList.menu.unmuteUserAudio.label": "Autoriser l'utilisateur à parler", "app.userList.userAriaLabel": "{0} {1} {2} État {3}", "app.userList.menu.promoteUser.label": "Promouvoir comme modérateur", - "app.userList.menu.demoteUser.label": "Rendre utilisateur normal", + "app.userList.menu.demoteUser.label": "Rétrograder en participant normal", "app.userList.menu.unlockUser.label": "Débloquer {0}", "app.userList.menu.lockUser.label": "Bloquer {0}", "app.userList.menu.directoryLookup.label": "Recherche dans l'annuaire", "app.userList.menu.makePresenter.label": "Définir comme présentateur", "app.userList.userOptions.manageUsersLabel": "Gérer les utilisateurs", - "app.userList.userOptions.muteAllLabel": "Mettre en sourdine tous les utilisateurs", - "app.userList.userOptions.muteAllDesc": "Met en sourdine tous les utilisateurs de la réunion", + "app.userList.userOptions.muteAllLabel": "Rendre muets tous les utilisateurs", + "app.userList.userOptions.muteAllDesc": "Rendre muets tous les utilisateurs de la réunion", "app.userList.userOptions.clearAllLabel": "Effacer toutes les icônes de statut", "app.userList.userOptions.clearAllDesc": "Effacer toutes les icônes de statut des utilisateurs", - "app.userList.userOptions.muteAllExceptPresenterLabel": "Mettre en sourdine tous les utilisateurs sauf le présentateur", - "app.userList.userOptions.muteAllExceptPresenterDesc": "Mettre en sourdine tous les utilisateurs de la réunion sauf le présentateur", - "app.userList.userOptions.unmuteAllLabel": "Désactiver la sourdine", - "app.userList.userOptions.unmuteAllDesc": "Désactiver la sourdine pour la réunion", - "app.userList.userOptions.lockViewersLabel": "Verrouiller les spectateurs", + "app.userList.userOptions.muteAllExceptPresenterLabel": "Rendre muets tous les utilisateurs sauf le présentateur", + "app.userList.userOptions.muteAllExceptPresenterDesc": "Rend muets tous les utilisateurs de la réunion sauf le présentateur", + "app.userList.userOptions.unmuteAllLabel": "Désactiver l'interdiction de parler pour la réunion", + "app.userList.userOptions.unmuteAllDesc": "Désactive l'interdiction de parler pour la réunion", + "app.userList.userOptions.lockViewersLabel": "Verrouiller les participants", "app.userList.userOptions.lockViewersDesc": "Verrouiller certaines fonctionnalités pour les participants à la réunion", + "app.userList.userOptions.connectionStatusLabel": "État de la connexion", + "app.userList.userOptions.connectionStatusDesc": "Voir le status des utilisateurs connectés", "app.userList.userOptions.disableCam": "Les webcams des participants sont désactivées", "app.userList.userOptions.disableMic": "Les microphones des participants sont désactivés", "app.userList.userOptions.disablePrivChat": "La discussion privée est désactivée", @@ -102,40 +105,43 @@ "app.userList.userOptions.disableNote": "Les notes partagées sont maintenant verrouillées", "app.userList.userOptions.hideUserList": "La liste des utilisateurs est maintenant masquée pour les participants", "app.userList.userOptions.webcamsOnlyForModerator": "Seuls les modérateurs peuvent voir les webcams des participants (selon les paramètres de verrouillage)", - "app.userList.content.participants.options.clearedStatus": "Effacé tout statut d'utilisateur", + "app.userList.content.participants.options.clearedStatus": "Tous les statuts d'utilisateurs sont effacés", "app.userList.userOptions.enableCam": "Les webcams des participants sont activées", "app.userList.userOptions.enableMic": "Les microphones des participants sont activés", "app.userList.userOptions.enablePrivChat": "La discussion privée est activée", "app.userList.userOptions.enablePubChat": "La discussion publique est activée", "app.userList.userOptions.enableNote": "Les notes partagées sont maintenant activées", - "app.userList.userOptions.showUserList": "La liste des utilisateurs est maintenant affichée aux téléspectateurs", + "app.userList.userOptions.showUserList": "La liste des utilisateurs est maintenant affichée aux participants", "app.userList.userOptions.enableOnlyModeratorWebcam": "Vous pouvez activer votre webcam maintenant, tout le monde vous verra", "app.media.label": "Média", "app.media.autoplayAlertDesc": "Autoriser l'accès", - "app.media.screenshare.start": "Le Partage d'écran a commencé", - "app.media.screenshare.end": "Le Partage d'écran s'est terminé", + "app.media.screenshare.start": "Partage d'écran commencé", + "app.media.screenshare.end": "Partage d'écran terminé", "app.media.screenshare.unavailable": "Partage d'écran indisponible", "app.media.screenshare.notSupported": "Le partage d'écran n'est pas supporté dans ce navigateur.", "app.media.screenshare.autoplayBlockedDesc": "Nous avons besoin de votre permission pour vous montrer l'écran du présentateur.", - "app.media.screenshare.autoplayAllowLabel": "Afficher l'écran partagé", + "app.media.screenshare.autoplayAllowLabel": "Voir l'écran partagé", "app.screenshare.notAllowed": "Erreur : l'autorisation d'accès à l'écran n'a pas été accordée.", "app.screenshare.notSupportedError": "Erreur : le partage d'écran est autorisé uniquement sur les domaines sécurisés (SSL)", "app.screenshare.notReadableError": "Erreur : un échec s'est produit lors de la capture de votre écran.", - "app.screenshare.genericError": "Erreur : une erreur s'est produite lors du partage d'écran, veuillez réessayer.", + "app.screenshare.genericError": "Erreur : une erreur s'est produite lors du partage d'écran, veuillez réessayer", "app.meeting.ended": "Cette session s'est terminée", "app.meeting.meetingTimeRemaining": "Temps de réunion restant : {0}", "app.meeting.meetingTimeHasEnded": "Le temps s'est écoulé. La réunion sera bientôt close", "app.meeting.endedMessage": "Vous serez redirigé vers l'écran d'accueil", - "app.meeting.alertMeetingEndsUnderOneMinute": "La réunion se termine dans une minute.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "La réunion privée se ferme dans une minute.", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "La conférence se fermera dans une minute.", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "La conférence se fermera dans {0} minutes.", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "La salle privée se fermera dans {0} minutes.", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "La salle privée se fermera dans une minute.", "app.presentation.hide": "Masquer la présentation", "app.presentation.notificationLabel": "Présentation courante", + "app.presentation.downloadLabel": "Télécharger", "app.presentation.slideContent": "Contenu de la diapositive", "app.presentation.startSlideContent": "Début du contenu de la diapositive", "app.presentation.endSlideContent": "Fin du contenu de la diapositive", "app.presentation.emptySlideContent": "Pas de contenu pour la diapositive actuelle", - "app.presentation.presentationToolbar.noNextSlideDesc": "Début de présentation", - "app.presentation.presentationToolbar.noPrevSlideDesc": "Fin de présentation", + "app.presentation.presentationToolbar.noNextSlideDesc": "Fin de présentation", + "app.presentation.presentationToolbar.noPrevSlideDesc": "Début de présentation", "app.presentation.presentationToolbar.selectLabel": "Sélectionnez la diapositive", "app.presentation.presentationToolbar.prevSlideLabel": "Diapositive précédente", "app.presentation.presentationToolbar.prevSlideDesc": "Changer la présentation à la diapositive précédente", @@ -168,12 +174,13 @@ "app.presentationUploder.dropzoneLabel": "Faites glisser les fichiers ici pour les charger", "app.presentationUploder.dropzoneImagesLabel": "Faites glisser les images ici pour les charger", "app.presentationUploder.browseFilesLabel": "ou parcourez pour trouver des fichiers", - "app.presentationUploder.browseImagesLabel": "ou parcourir/capturer des images", + "app.presentationUploder.browseImagesLabel": "ou parcourez/capturez des images", "app.presentationUploder.fileToUpload": "Prêt à être chargé...", "app.presentationUploder.currentBadge": "En cours", - "app.presentationUploder.rejectedError": "Le(s) fichier(s) sélectionné(s) a été rejeté(s). Veuillez vérifier le format de ce(s) fichier(s).", + "app.presentationUploder.rejectedError": "Le ou les fichiers sélectionnés ont été rejetés. Veuillez vérifier leurs formats.", "app.presentationUploder.upload.progress": "Chargement ({0}%)", - "app.presentationUploder.upload.413": "Le fichier est trop volumineux. Veuillez le diviser en plusieurs fichiers s'il vous plaît.", + "app.presentationUploder.upload.413": "Le fichier est trop volumineux. Veuillez le diviser en plusieurs fichiers.", + "app.presentationUploder.genericError": "Oups, quelque chose s'est mal passé...", "app.presentationUploder.upload.408": "Le jeton de demande de téléversement a expiré.", "app.presentationUploder.upload.404": "404 : jeton de téléversement invalide", "app.presentationUploder.upload.401": "La demande d'un jeton de téléversement de présentation a échoué.", @@ -183,31 +190,36 @@ "app.presentationUploder.conversion.generatedSlides": "Diapositives générées...", "app.presentationUploder.conversion.generatingSvg": "Génération des images SVG...", "app.presentationUploder.conversion.pageCountExceeded": "Nombre de pages dépassés. Merci de diviser le fichier en plusieurs fichiers.", - "app.presentationUploder.conversion.officeDocConversionInvalid": "Échec du traitement du document office. Merci de télécharger un PDF à la place.", - "app.presentationUploder.conversion.officeDocConversionFailed": "Échec du traitement du document office. Merci de télécharger un PDF à la place.", - "app.presentationUploder.conversion.pdfHasBigPage": "Nous n'avons pas pu convertir le fichier PDF, veuillez essayer de l'optimiser.", - "app.presentationUploder.conversion.timeout": "Oops, la conversion a pris trop de temps", + "app.presentationUploder.conversion.officeDocConversionInvalid": "Échec du traitement du document Office. Veuillez télécharger un PDF à la place.", + "app.presentationUploder.conversion.officeDocConversionFailed": "Échec du traitement du document office. Veuillez télécharger un PDF à la place.", + "app.presentationUploder.conversion.pdfHasBigPage": "Nous n'avons pas pu convertir le fichier PDF, veuillez essayer de l'optimiser", + "app.presentationUploder.conversion.timeout": "Oups, la conversion a pris trop de temps", "app.presentationUploder.conversion.pageCountFailed": "Impossible de déterminer le nombre de pages.", "app.presentationUploder.isDownloadableLabel": "Ne pas autoriser le téléchargement de la présentation", "app.presentationUploder.isNotDownloadableLabel": "Autoriser le téléchargement de la présentation", "app.presentationUploder.removePresentationLabel": "Supprimer la présentation", - "app.presentationUploder.setAsCurrentPresentation": "Définir la présentation comme courante", + "app.presentationUploder.setAsCurrentPresentation": "Définir la présentation comme celle en cours", "app.presentationUploder.tableHeading.filename": "Nom de fichier", "app.presentationUploder.tableHeading.options": "Options", "app.presentationUploder.tableHeading.status": "Statut", + "app.presentationUploder.uploading": "Charger {0} {1}", + "app.presentationUploder.uploadStatus": "{0} sur {1} chargements terminés", + "app.presentationUploder.completed": "{0} chargements terminés", + "app.presentationUploder.item" : "élément", + "app.presentationUploder.itemPlural" : "éléments", "app.poll.pollPaneTitle": "Sondage", - "app.poll.quickPollTitle": "Sondage Rapide", + "app.poll.quickPollTitle": "Sondage rapide", "app.poll.hidePollDesc": "Masque le volet du menu du sondage", "app.poll.customPollInstruction": "Pour créer un sondage personnalisé, sélectionnez le bouton ci-dessous et entrez vos options.", "app.poll.quickPollInstruction": "Sélectionnez une option ci-dessous pour démarrer votre sondage.", "app.poll.customPollLabel": "Sondage personnalisé", "app.poll.startCustomLabel": "Démarrez un sondage personnalisé", - "app.poll.activePollInstruction": "Laissez cette fenêtre ouverte afin de voir les réponses en direct à votre sondage. Lorsque vous êtes prêt, sélectionnez 'Publier les résultats du sondage' pour publier les résultats et terminer le sondage.", + "app.poll.activePollInstruction": "Laissez cette fenêtre ouverte afin de voir en direct les réponses à votre sondage. Lorsque vous êtes prêt, sélectionnez « Publier les résultats du sondage » pour publier les résultats et terminer le sondage.", "app.poll.publishLabel": "Publier les résultats du sondage", "app.poll.backLabel": "Retour aux options de sondage", "app.poll.closeLabel": "Fermer", "app.poll.waitingLabel": "En attente des réponses ({0}/{1})", - "app.poll.ariaInputCount": "Option de sondage personnalisé {0} de {1} ", + "app.poll.ariaInputCount": "Option {0} sur {1} du sondage personnalisé", "app.poll.customPlaceholder": "Ajouter une option de sondage", "app.poll.noPresentationSelected": "Aucune présentation sélectionnée ! Veuillez en sélectionner une.", "app.poll.clickHereToSelect": "Cliquez ici pour sélectionner", @@ -242,7 +254,7 @@ "app.retryNow": "Réessayer maintenant", "app.navBar.settingsDropdown.optionsLabel": "Options", "app.navBar.settingsDropdown.fullscreenLabel": "Plein écran", - "app.navBar.settingsDropdown.settingsLabel": "Ouvrir les paramètres", + "app.navBar.settingsDropdown.settingsLabel": "Paramètres", "app.navBar.settingsDropdown.aboutLabel": "À propos", "app.navBar.settingsDropdown.leaveSessionLabel": "Déconnexion", "app.navBar.settingsDropdown.exitFullscreenLabel": "Quitter le plein écran", @@ -254,10 +266,10 @@ "app.navBar.settingsDropdown.hotkeysLabel": "Raccourcis clavier", "app.navBar.settingsDropdown.hotkeysDesc": "Liste des raccourcis clavier disponibles", "app.navBar.settingsDropdown.helpLabel": "Aide", - "app.navBar.settingsDropdown.helpDesc": "Lien utilisateur vers les tutoriels vidéo (ouvre un nouvel onglet)", + "app.navBar.settingsDropdown.helpDesc": "Liens utilisateur vers les tutoriels vidéos (ouvre un nouvel onglet)", "app.navBar.settingsDropdown.endMeetingDesc": "Termine la réunion en cours", "app.navBar.settingsDropdown.endMeetingLabel": "Mettre fin à la réunion", - "app.navBar.userListToggleBtnLabel": "Basculer l'affichage sur la Liste des Utilisateurs", + "app.navBar.userListToggleBtnLabel": "Basculer l'affichage sur la liste des utilisateurs", "app.navBar.toggleUserList.ariaLabel": "Basculer sur les utilisateurs et les messages", "app.navBar.toggleUserList.newMessages": "avec notification des nouveaux messages", "app.navBar.recording": "Cette session est enregistrée", @@ -267,7 +279,7 @@ "app.leaveConfirmation.confirmLabel": "Quitter", "app.leaveConfirmation.confirmDesc": "Vous déconnecte de la conférence", "app.endMeeting.title": "Mettre fin à la réunion", - "app.endMeeting.description": "Êtes-vous sûr de vouloir terminer cette session ?", + "app.endMeeting.description": "Voulez-vous vraiment fermer cette conférence pour tout le monde (tous les utilisateurs seront déconnectés) ?", "app.endMeeting.yesLabel": "Oui", "app.endMeeting.noLabel": "Non", "app.about.title": "À propos", @@ -278,7 +290,7 @@ "app.about.dismissLabel": "Annuler", "app.about.dismissDesc": "Fermer l'information client", "app.actionsBar.changeStatusLabel": "Changer statut", - "app.actionsBar.muteLabel": "Rendre silencieux", + "app.actionsBar.muteLabel": "Rendre muet", "app.actionsBar.unmuteLabel": "Autoriser à parler", "app.actionsBar.camOffLabel": "Caméra éteinte", "app.actionsBar.raiseLabel": "Lever la main", @@ -288,18 +300,20 @@ "app.screenshare.screenShareLabel" : "Partage d'écran", "app.submenu.application.applicationSectionTitle": "Application", "app.submenu.application.animationsLabel": "Animations", - "app.submenu.application.audioAlertLabel": "Alertes audio pour Discussion", - "app.submenu.application.pushAlertLabel": "Alertes Popup de Discussion", - "app.submenu.application.userJoinAudioAlertLabel": "Alertes audio pour Nouveau participant", - "app.submenu.application.userJoinPushAlertLabel": "Alertes Popup pour Nouveau participant", "app.submenu.application.fontSizeControlLabel": "Taille des caractères", - "app.submenu.application.increaseFontBtnLabel": "Augmenter la Taille de la Police", - "app.submenu.application.decreaseFontBtnLabel": "Diminuer la Taille de la Police", + "app.submenu.application.increaseFontBtnLabel": "Augmenter la taille de la police", + "app.submenu.application.decreaseFontBtnLabel": "Diminuer la taille de la police", "app.submenu.application.currentSize": "actuellement {0}", "app.submenu.application.languageLabel": "Langue de l'application", "app.submenu.application.languageOptionLabel": "Choisir la langue", - "app.submenu.application.noLocaleOptionLabel": "Pas de lieu actif", - "app.submenu.audio.micSourceLabel": "Source du Micro", + "app.submenu.application.noLocaleOptionLabel": "Pas de « locales » actives", + "app.submenu.notification.SectionTitle": "Notifications", + "app.submenu.notification.Desc": "Définissez comment et ce pour quoi vous serez averti.", + "app.submenu.notification.audioAlertLabel": "Alerte sonore", + "app.submenu.notification.pushAlertLabel": "Message d'alerte", + "app.submenu.notification.messagesLabel": "Fil de discussion", + "app.submenu.notification.userJoinLabel": "Rejoindre l'utilisateur", + "app.submenu.audio.micSourceLabel": "Source du micro", "app.submenu.audio.speakerSourceLabel": "Source du haut-parleur", "app.submenu.audio.streamVolumeLabel": "Volume de votre flux audio", "app.submenu.video.title": "Vidéo", @@ -322,9 +336,10 @@ "app.settings.dataSavingTab.screenShare": "Activer le partage de bureau", "app.settings.dataSavingTab.description": "Pour économiser votre bande passante, ajustez ce qui est actuellement affiché.", "app.settings.save-notification.label": "Les paramètres ont été enregistrés", + "app.statusNotifier.and": "et", "app.switch.onLabel": "ON", "app.switch.offLabel": "OFF", - "app.talkingIndicator.ariaMuteDesc" : "Sélectionner pour rendre muet", + "app.talkingIndicator.ariaMuteDesc" : "Sélectionner pour rendre muet l'utilisateur", "app.talkingIndicator.isTalking" : "{0} est en train de parler", "app.talkingIndicator.wasTalking" : "{0} a cessé de parler", "app.actionsBar.actionsDropdown.actionsLabel": "Actions", @@ -348,41 +363,41 @@ "app.actionsBar.actionsDropdown.takePresenterDesc": "S'assigner comme nouveau présentateur", "app.actionsBar.emojiMenu.statusTriggerLabel": "Définir le statut", "app.actionsBar.emojiMenu.awayLabel": "Éloigné", - "app.actionsBar.emojiMenu.awayDesc": "Passer votre état à éloigné", + "app.actionsBar.emojiMenu.awayDesc": "Passer votre état à « éloigné »", "app.actionsBar.emojiMenu.raiseHandLabel": "Lever la main", "app.actionsBar.emojiMenu.raiseHandDesc": "Lever la main pour poser une question", "app.actionsBar.emojiMenu.neutralLabel": "Indécis", - "app.actionsBar.emojiMenu.neutralDesc": "Passer votre état à indécis", + "app.actionsBar.emojiMenu.neutralDesc": "Passer votre état à « indécis »", "app.actionsBar.emojiMenu.confusedLabel": "Désorienté", - "app.actionsBar.emojiMenu.confusedDesc": "Passer votre état à désorienté", + "app.actionsBar.emojiMenu.confusedDesc": "Passer votre état à « désorienté »", "app.actionsBar.emojiMenu.sadLabel": "Triste", - "app.actionsBar.emojiMenu.sadDesc": "Passer votre état à triste", + "app.actionsBar.emojiMenu.sadDesc": "Passer votre état à « triste »", "app.actionsBar.emojiMenu.happyLabel": "Ravi", - "app.actionsBar.emojiMenu.happyDesc": "Passer votre état à ravi", + "app.actionsBar.emojiMenu.happyDesc": "Passer votre état à « ravi »", "app.actionsBar.emojiMenu.noneLabel": "Effacer votre état", "app.actionsBar.emojiMenu.noneDesc": "Effacer votre statut", "app.actionsBar.emojiMenu.applauseLabel": "Applaudissements", - "app.actionsBar.emojiMenu.applauseDesc": "Passer votre état à applaudissements", + "app.actionsBar.emojiMenu.applauseDesc": "Passer votre état à « applaudissements »", "app.actionsBar.emojiMenu.thumbsUpLabel": "Favorable", - "app.actionsBar.emojiMenu.thumbsUpDesc": "Passer votre statut à favorable", + "app.actionsBar.emojiMenu.thumbsUpDesc": "Passer votre statut à « favorable »", "app.actionsBar.emojiMenu.thumbsDownLabel": "Défavorable", - "app.actionsBar.emojiMenu.thumbsDownDesc": "Passer votre statut à défavorable", + "app.actionsBar.emojiMenu.thumbsDownDesc": "Passer votre statut à « défavorable »", "app.actionsBar.currentStatusDesc": "statut actuel {0}", "app.actionsBar.captions.start": "Démarrer l'affichage des sous-titres", "app.actionsBar.captions.stop": "Arrêter l'affichage des sous-titres", "app.audioNotification.audioFailedError1001": "WebSocket déconnecté (erreur 1001)", "app.audioNotification.audioFailedError1002": "Échec de la connexion WebSocket (erreur 1002)", - "app.audioNotification.audioFailedError1003": "Version du navigateur non supporté (erreur 1003)", + "app.audioNotification.audioFailedError1003": "Version du navigateur non supportée (erreur 1003)", "app.audioNotification.audioFailedError1004": "Échec lors de l'appel (raison={0}) (erreur 1004)", "app.audioNotification.audioFailedError1005": "L'appel s'est terminé de façon inattendue (erreur 1005)", "app.audioNotification.audioFailedError1006": "Délai d'appel dépassé (erreur 1006)", "app.audioNotification.audioFailedError1007": "Échec de la connexion (erreur ICE 1007)", - "app.audioNotification.audioFailedError1008": "Transfert échoué (erreur 1008)", + "app.audioNotification.audioFailedError1008": "Échec du transfert (erreur 1008)", "app.audioNotification.audioFailedError1009": "impossible de récupérer les informations du serveur STUN/TURN (erreur 1009)", "app.audioNotification.audioFailedError1010": "Délai dépassé durant la négociation (erreur ICE 1010)", "app.audioNotification.audioFailedError1011": "Délai d'attente de connexion dépassé (erreur ICE 1011)", "app.audioNotification.audioFailedError1012": "Connexion fermée (erreur ICE 1012)", - "app.audioNotification.audioFailedMessage": "Votre connexion audio à échoué", + "app.audioNotification.audioFailedMessage": "Votre connexion audio a échoué", "app.audioNotification.mediaFailedMessage": "getUserMicMedia a échoué car seules les origines sécurisées sont autorisées", "app.audioNotification.closeLabel": "Fermer", "app.audioNotificaion.reconnectingAsListenOnly": "Le microphone est verrouillé pour les participants, vous êtes connecté en mode écoute uniquement.", @@ -409,7 +424,7 @@ "app.audioModal.no": "Non", "app.audioModal.yes.arialabel" : "Écho activé", "app.audioModal.no.arialabel" : "Écho désactivé", - "app.audioModal.echoTestTitle": "Ceci est un test d'écho privé. Prononcez quelques mots. Avez-vous entendu de l'audio ?", + "app.audioModal.echoTestTitle": "Ceci est un test d'écho privé. Prononcez quelques mots. Les avez-vous entendus ?", "app.audioModal.settingsTitle": "Modifier vos paramètres audio", "app.audioModal.helpTitle": "Il y a un problème avec vos périphériques", "app.audioModal.helpText": "Avez-vous donné la permission d'accéder à votre microphone ? Notez qu'une boîte de dialogue doit apparaître lorsque vous essayez de rejoindre l'audio, demandant les autorisations de votre périphérique multimédia. Veuillez l'accepter pour rejoindre la conférence audio. Si ce n'est pas le cas, essayez de modifier les autorisations de votre microphone dans les paramètres de votre navigateur.", @@ -429,31 +444,31 @@ "app.audioManager.joinedEcho": "Vous avez rejoint le test d'écho", "app.audioManager.leftAudio": "Vous avez quitté la conférence audio", "app.audioManager.reconnectingAudio": "Tentative de reconnexion audio", - "app.audioManager.genericError": "Erreur : une erreur s'est produite, veuillez réessayez.", + "app.audioManager.genericError": "Erreur : une erreur s'est produite, veuillez réessayez", "app.audioManager.connectionError": "Erreur : erreur de connexion", "app.audioManager.requestTimeout": "Erreur : un délai est dépassé dans la requête", "app.audioManager.invalidTarget": "Erreur : tentative de requête sur une destination invalide", "app.audioManager.mediaError": "Erreur : il y a un problème pour obtenir vos périphériques multimédias", - "app.audio.joinAudio": "Rejoindre l'Audio", - "app.audio.leaveAudio": "Quitter l'Audio", + "app.audio.joinAudio": "Rejoindre l'audio", + "app.audio.leaveAudio": "Quitter l'audio", "app.audio.enterSessionLabel": "Entrer une session", "app.audio.playSoundLabel": "Jouer son", "app.audio.backLabel": "Retour", "app.audio.audioSettings.titleLabel": "Choisissez vos paramètres audio", "app.audio.audioSettings.descriptionLabel": "Veuillez noter qu'une boîte de dialogue apparaîtra dans votre navigateur, vous demandant d'accepter le partage de votre micro.", - "app.audio.audioSettings.microphoneSourceLabel": "Source du Micro", + "app.audio.audioSettings.microphoneSourceLabel": "Source du micro", "app.audio.audioSettings.speakerSourceLabel": "Source du haut-parleur", "app.audio.audioSettings.microphoneStreamLabel": "Le volume de votre flux audio", "app.audio.audioSettings.retryLabel": "Réessayer", "app.audio.listenOnly.backLabel": "Retour", "app.audio.listenOnly.closeLabel": "Fermer", "app.audio.permissionsOverlay.title": "Autoriser BigBlueButton à utiliser vos périphériques multimédias", - "app.audio.permissionsOverlay.hint": "Autorisez-nous pour nous permettre d’utiliser vos appareils multimédias, afin de vous joindre à la conférence vocale :)", + "app.audio.permissionsOverlay.hint": "Il est nécessaire que vous nous autorisiez à utiliser vos appareils multimédias pour que vous puissiez rejoindre la conférence vocale :)", "app.error.removed": "Vous avez été retiré de la conférence", "app.error.meeting.ended": "Vous avez été déconnecté de la conférence", "app.meeting.logout.duplicateUserEjectReason": "Utilisateur en double essayant de rejoindre une réunion", "app.meeting.logout.permissionEjectReason": "Éjecté en raison d'une violation de permission", - "app.meeting.logout.ejectedFromMeeting": "Vous avez été éjecté de la réunion", + "app.meeting.logout.ejectedFromMeeting": "Vous avez été retiré de la réunion", "app.meeting.logout.validateTokenFailedEjectReason": "Échec de la validation du jeton d'autorisation", "app.meeting.logout.userInactivityEjectReason": "Utilisateur inactif trop longtemps", "app.meeting-ended.rating.legendLabel": "Évaluation", @@ -464,9 +479,9 @@ "app.modal.newTab": "(ouvre un nouvel onglet)", "app.modal.confirm.description": "Sauvegarde les changements et ferme la fenêtre modale", "app.dropdown.close": "Fermer", - "app.error.400": "Mauvaise Requête", + "app.error.400": "Mauvaise requête", "app.error.401": "Non autorisé", - "app.error.403": "Vous avez été éjecté de la réunion", + "app.error.403": "Vous avez été retiré de la réunion", "app.error.404": "Non trouvé", "app.error.410": "La conférence est terminée", "app.error.500": "Oups, quelque chose s'est mal passé", @@ -482,24 +497,27 @@ "app.userList.guest.allowAllGuests": "Autoriser tous les invités", "app.userList.guest.allowEveryone": "Autoriser tout le monde", "app.userList.guest.denyEveryone": "Rejeter tout le monde", - "app.userList.guest.pendingUsers": "{0} Utilisateurs en attente", - "app.userList.guest.pendingGuestUsers": "{0} Utilisateurs invités en attente", + "app.userList.guest.pendingUsers": "{0} utilisateurs en attente", + "app.userList.guest.pendingGuestUsers": "{0} utilisateurs invités en attente", "app.userList.guest.pendingGuestAlert": "A rejoint la session et attend votre approbation.", "app.userList.guest.rememberChoice": "Se rappeler du choix", + "app.userList.guest.acceptLabel": "Accepter", + "app.userList.guest.denyLabel": "Refuser", "app.user-info.title": "Recherche dans l'annuaire", "app.toast.breakoutRoomEnded": "La réunion privée s'est terminée. Veuillez rejoindre l'audio.", "app.toast.chat.public": "Nouveau message de discussion publique", "app.toast.chat.private": "Nouveau message de discussion privée", "app.toast.chat.system": "Système", - "app.toast.clearedEmoji.label": "Statut Emoji effacé", - "app.toast.setEmoji.label": "Statut Emoji défini sur {0}", - "app.toast.meetingMuteOn.label": "Tous les utilisateurs ont été mis en sourdine", - "app.toast.meetingMuteOff.label": "Réunion muet désactivé", + "app.toast.clearedEmoji.label": "Statut emoticône effacé", + "app.toast.setEmoji.label": "Statut emoticône défini sur {0}", + "app.toast.meetingMuteOn.label": "Tous les utilisateurs ont été rendus muets", + "app.toast.meetingMuteOff.label": "Réunion muette désactivée", "app.notification.recordingStart": "Cette session est maintenant enregistrée", "app.notification.recordingStop": "Cette session n'est pas enregistrée", "app.notification.recordingPaused": "Cette session n'est maintenant plus enregistrée", "app.notification.recordingAriaLabel": "Temps enregistré", "app.notification.userJoinPushAlert": "{0} s'est joint à la session", + "app.submenu.notification.raiseHandLabel": "Lever la main", "app.shortcut-help.title": "Raccourcis clavier", "app.shortcut-help.accessKeyNotAvailable": "Clés d'accès non disponibles", "app.shortcut-help.comboLabel": "Combo", @@ -508,42 +526,55 @@ "app.shortcut-help.closeDesc": "Ferme la fenêtre modale des raccourcis clavier", "app.shortcut-help.openOptions": "Ouvrir les options", "app.shortcut-help.toggleUserList": "Basculer la liste d'utilisateurs", - "app.shortcut-help.toggleMute": "Assourdir / Activer", - "app.shortcut-help.togglePublicChat": "Basculer la discussion publique (la liste des utilisateurs doit être ouverte)", + "app.shortcut-help.toggleMute": "Activer / Désactiver le mode muet", + "app.shortcut-help.togglePublicChat": "Basculer la discussion en mode discussion publique (la liste des utilisateurs doit être ouverte)", "app.shortcut-help.hidePrivateChat": "Masquer la discussion privée", "app.shortcut-help.closePrivateChat": "Fermer la discussion privée", "app.shortcut-help.openActions": "Ouvrir le menu des actions", "app.shortcut-help.openStatus": "Ouvrir le menu de statut", - "app.shortcut-help.togglePan": "Activer l'outil Panoramique (Présentateur)", - "app.shortcut-help.nextSlideDesc": "Diapositive suivante (Présentateur)", - "app.shortcut-help.previousSlideDesc": "Diapositive précédente (Présentateur)", - "app.lock-viewers.title": "Verrouiller les spectateurs", - "app.lock-viewers.description": "Ces options vous permettent de restreindre l'utilisation de certaines fonctions par les utilisateurs.", + "app.shortcut-help.togglePan": "Activer l'outil panoramique (présentateur)", + "app.shortcut-help.nextSlideDesc": "Diapositive suivante (présentateur)", + "app.shortcut-help.previousSlideDesc": "Diapositive précédente (présentateur)", + "app.lock-viewers.title": "Verrouiller les participants", + "app.lock-viewers.description": "Ces options vous permettent de restreindre l'utilisation de certaines fonctionnalités par les participants.", "app.lock-viewers.featuresLable": "Fonctionnalité", "app.lock-viewers.lockStatusLabel": "Statut", "app.lock-viewers.webcamLabel": "Partager webcam", - "app.lock-viewers.otherViewersWebcamLabel": "Voir les webcams d'autres participants ", + "app.lock-viewers.otherViewersWebcamLabel": "Voir les webcams des autres participants ", "app.lock-viewers.microphoneLable": "Partager le microphone", "app.lock-viewers.PublicChatLabel": "Envoyer des messages de discussion publique", "app.lock-viewers.PrivateChatLable": "Envoyer des messages de discussion privée", "app.lock-viewers.notesLabel": "Éditer les notes partagées", - "app.lock-viewers.userListLabel": "Voir les autres participants dans la liste Utilisateurs", + "app.lock-viewers.userListLabel": "Voir les autres participants dans la liste des utilisateurs", "app.lock-viewers.ariaTitle": "Verrouiller la fenêtre de paramètres des participants", "app.lock-viewers.button.apply": "Appliquer", "app.lock-viewers.button.cancel": "Annuler", "app.lock-viewers.locked": "Verrouillé", "app.lock-viewers.unlocked": "Déverrouillé", + "app.connection-status.ariaTitle": "État de la connexion visible", + "app.connection-status.title": "État de la connexion", + "app.connection-status.description": "Voir l'état de la connexion des utilisateurs", + "app.connection-status.empty": "Aucun problème de connectivité n'a été signalé jusqu'à présent", + "app.connection-status.more": "plus", + "app.connection-status.offline": "Déconnecté", "app.recording.startTitle": "Commencer l'enregistrement", "app.recording.stopTitle": "Enregistrement en pause", "app.recording.resumeTitle": "Reprendre l'enregistrement", - "app.recording.startDescription": "Vous pouvez utiliser à nouveau le bouton d'enregistrement ultérieurement pour mettre l'enregistrement en pause.", + "app.recording.startDescription": "Vous pouvez à nouveau utiliser le bouton d'enregistrement ultérieurement pour mettre l'enregistrement en pause.", "app.recording.stopDescription": "Êtes-vous sûr de vouloir mettre l'enregistrement en pause ? Vous pouvez reprendre en utilisant à nouveau le bouton d'enregistrement.", "app.videoPreview.cameraLabel": "Caméra", "app.videoPreview.profileLabel": "Qualité", + "app.videoPreview.quality.low": "Bas", + "app.videoPreview.quality.medium": "Moyen", + "app.videoPreview.quality.high": "Haut", + "app.videoPreview.quality.hd": "Haute définition", "app.videoPreview.cancelLabel": "Annuler", "app.videoPreview.closeLabel": "Fermer", "app.videoPreview.findingWebcamsLabel": "Recherche de webcams", "app.videoPreview.startSharingLabel": "Commencer à partager", + "app.videoPreview.stopSharingLabel": "Arrêter le partage", + "app.videoPreview.stopSharingAllLabel": "Arrêter tout", + "app.videoPreview.sharedCameraLabel": "Cette caméra est déjà utilisée", "app.videoPreview.webcamOptionLabel": "Sélectionner une webcam", "app.videoPreview.webcamPreviewLabel": "Aperçu de la webcam", "app.videoPreview.webcamSettingsTitle": "Paramètres de la webcam", @@ -555,12 +586,12 @@ "app.video.iceConnectionStateError": "Échec de connexion (erreur ICE 1107)", "app.video.permissionError": "Erreur lors du partage de la webcam. Veuillez vérifier les permissions", "app.video.sharingError": "Erreur lors du partage de la Webcam", - "app.video.notFoundError": "Webcam introuvable. Assurez-vous qu'elle soit bien connectée", + "app.video.notFoundError": "Webcam introuvable. Assurez-vous qu'elle est bien connectée", "app.video.notAllowed": "Permission manquante pour partager la Webcam. Veuillez vérifier les permissions dans votre navigateur", - "app.video.notSupportedError": "La vidéo de la webcam peut uniquement être partagée avec des sources sûres ; assurez-vous que votre certificat SSL est valide", + "app.video.notSupportedError": "La vidéo de la webcam peut être partagée uniquement avec des sources sûres ; assurez-vous que votre certificat SSL est valide", "app.video.notReadableError": "Impossible d'obtenir la vidéo de la webcam. Assurez-vous qu'aucun autre programme n'utilise la webcam", "app.video.mediaFlowTimeout1020": "Le média n'a pas pu atteindre le serveur (erreur 1020)", - "app.video.suggestWebcamLock": "Appliquer le paramètre de verrouillage aux webcams des téléspectateurs ?", + "app.video.suggestWebcamLock": "Appliquer le paramètre de verrouillage aux webcams des participants ?", "app.video.suggestWebcamLockReason": "(cela améliorera la stabilité de la conférence)", "app.video.enable": "Activer", "app.video.cancel": "Annuler", @@ -568,39 +599,29 @@ "app.video.swapCamDesc": "Permuter les Webcams", "app.video.videoLocked": "Partage webcam verrouillé", "app.video.videoButtonDesc": "Partager webcam", - "app.video.videoMenu": "Menu Vidéo", + "app.video.videoMenu": "Menu vidéo", "app.video.videoMenuDisabled": "Le menu vidéo de la webcam est désactivé dans les paramètres", "app.video.videoMenuDesc": "Ouvrir le menu déroulant de la vidéo", "app.video.chromeExtensionError": "Vous devez installer ", "app.video.chromeExtensionErrorLink": "cette extension Chrome", - "app.video.stats.title": "Statistiques de connexion", - "app.video.stats.packetsReceived": "Paquets reçus", - "app.video.stats.packetsSent": "Paquets envoyés", - "app.video.stats.packetsLost": "Paquets perdus", - "app.video.stats.bitrate": "Bitrate", - "app.video.stats.lostPercentage": "Pourcentage perdu total", - "app.video.stats.lostRecentPercentage": "Pourcentage perdu récent", - "app.video.stats.dimensions": "Dimensions", - "app.video.stats.codec": "Codec", - "app.video.stats.decodeDelay": "Délai de décodage", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Usage de l'encodage", - "app.video.stats.currentDelay": "Délai actuel", - "app.fullscreenButton.label": "Rendre {0} plein écran", - "app.deskshare.iceConnectionStateError": "Connexion échoué lors du partage d'écran (erreur ICE 1108)", + "app.video.pagination.prevPage": "Voir les vidéos précédentes", + "app.video.pagination.nextPage": "Voir les vidéos suivantes", + "app.fullscreenButton.label": "Mettre {0} en plein écran", + "app.deskshare.iceConnectionStateError": "Échec de connexion lors du partage d'écran (erreur ICE 1108)", "app.sfu.mediaServerConnectionError2000": "Impossible de se connecter au serveur multimédia (erreur 2000)", "app.sfu.mediaServerOffline2001": "Le serveur multimédia est hors ligne. Veuillez réessayer plus tard (erreur 2001)", - "app.sfu.mediaServerNoResources2002": "Le serveur multimédia n'est plus de ressources disponibles (erreur 2002)", + "app.sfu.mediaServerNoResources2002": "Le serveur multimédia n'a plus de ressources disponibles (erreur 2002)", "app.sfu.mediaServerRequestTimeout2003": "Les demandes du serveur multimédia expirent (erreur 2003)", "app.sfu.serverIceGatheringFailed2021": "Le serveur multimédia ne peut pas rassembler les candidats de connexion (erreur ICE 2021)", - "app.sfu.serverIceGatheringFailed2022": "Connexion au serveur multimédia échouée (erreur ICE 2022)", + "app.sfu.serverIceGatheringFailed2022": "Échec de connexion au serveur multimédia (erreur ICE 2022)", "app.sfu.mediaGenericError2200": "Le serveur multimédia n'a pas pu traiter la demande (erreur 2200)", "app.sfu.invalidSdp2202":"Le client a généré une requête multimédia invalide (erreur SDP 2202)", "app.sfu.noAvailableCodec2203": "Le serveur n'a pas trouvé de codec approprié (erreur 2203)", "app.meeting.endNotification.ok.label": "OK", "app.whiteboard.annotations.poll": "Les résultats du sondage ont été publiés", + "app.whiteboard.annotations.pollResult": "Résultats du sondage", "app.whiteboard.toolbar.tools": "Outils", - "app.whiteboard.toolbar.tools.hand": "Pan", + "app.whiteboard.toolbar.tools.hand": "Panoramique", "app.whiteboard.toolbar.tools.pencil": "Crayon", "app.whiteboard.toolbar.tools.rectangle": "Rectangle", "app.whiteboard.toolbar.tools.triangle": "Triangle", @@ -608,9 +629,9 @@ "app.whiteboard.toolbar.tools.line": "Ligne", "app.whiteboard.toolbar.tools.text": "Texte", "app.whiteboard.toolbar.thickness": "Épaisseur du dessin ", - "app.whiteboard.toolbar.thicknessDisabled": "Épaisseur du dessin désactivé", + "app.whiteboard.toolbar.thicknessDisabled": "Épaisseur du dessin désactivée", "app.whiteboard.toolbar.color": "Couleurs", - "app.whiteboard.toolbar.colorDisabled": "Les couleurs sont désactivées", + "app.whiteboard.toolbar.colorDisabled": "Couleurs désactivées", "app.whiteboard.toolbar.color.black": "Noir", "app.whiteboard.toolbar.color.white": "Blanc", "app.whiteboard.toolbar.color.red": "Rouge", @@ -627,17 +648,17 @@ "app.whiteboard.toolbar.clear": "Effacer toutes les annotations", "app.whiteboard.toolbar.multiUserOn": "Activer le mode multi-utilisateur", "app.whiteboard.toolbar.multiUserOff": "Désactiver le mode multi-utilisateur", - "app.whiteboard.toolbar.fontSize": "Liste de taille de police", + "app.whiteboard.toolbar.fontSize": "Liste de tailles de police", "app.feedback.title": "Vous avez quitté la conférence", - "app.feedback.subtitle": "Nous aimerions connaitre votre expérience avec BigBlueButton (optionnel)", - "app.feedback.textarea": "Comment pouvons nous améliorer BigBlueButton ?", + "app.feedback.subtitle": "Nous aimerions beaucoup savoir ce que vous pensez de votre expérience avec BigBlueButton (optionnel)", + "app.feedback.textarea": "Comment pouvons-nous améliorer BigBlueButton ?", "app.feedback.sendFeedback": "Envoyer l'avis", "app.feedback.sendFeedbackDesc": "Envoyer une évaluation et quitter la réunion", "app.videoDock.webcamFocusLabel": "Focus", "app.videoDock.webcamFocusDesc": "Focus sur la webcam sélectionnée", "app.videoDock.webcamUnfocusLabel": "Arrêt du focus", "app.videoDock.webcamUnfocusDesc": "Arrêt du focus sur la webcam sélectionnée", - "app.videoDock.autoplayBlockedDesc": "Nous avons besoin de votre permission pour vous montrer les webcams d'autres utilisateurs.", + "app.videoDock.autoplayBlockedDesc": "Nous avons besoin de votre permission pour vous montrer les webcams des autres utilisateurs.", "app.videoDock.autoplayAllowLabel": "Voir les webcams", "app.invitation.title": "Invitation à une réunion privée", "app.invitation.confirm": "Inviter", @@ -650,11 +671,11 @@ "app.createBreakoutRoom.room": "Réunion {0}", "app.createBreakoutRoom.notAssigned": "Non attribué ({0})", "app.createBreakoutRoom.join": "Rejoindre la réunion", - "app.createBreakoutRoom.joinAudio": "Rejoindre l'Audio", + "app.createBreakoutRoom.joinAudio": "Rejoindre l'audio", "app.createBreakoutRoom.returnAudio": "Retour audio", "app.createBreakoutRoom.alreadyConnected": "Déjà dans la salle", "app.createBreakoutRoom.confirm": "Créer", - "app.createBreakoutRoom.record": "Enregistre", + "app.createBreakoutRoom.record": "Enregistrer", "app.createBreakoutRoom.numberOfRooms": "Nombre de réunions", "app.createBreakoutRoom.durationInMinutes": "Durée (minutes)", "app.createBreakoutRoom.randomlyAssign": "Assigner au hasard", @@ -665,7 +686,7 @@ "app.createBreakoutRoom.minusRoomTime": "Diminuer le temps de la réunion privée à ", "app.createBreakoutRoom.addRoomTime": "Augmenter le temps de la réunion privée à ", "app.createBreakoutRoom.addParticipantLabel": "+ Ajouter participant", - "app.createBreakoutRoom.freeJoin": "Autoriser les participants à choisir une salle de réunion à rejoindre", + "app.createBreakoutRoom.freeJoin": "Autoriser les participants à choisir la salle de réunion qu'ils souhaitent rejoindre", "app.createBreakoutRoom.leastOneWarnBreakout": "Vous devez placer au moins un participant dans une réunion privée.", "app.createBreakoutRoom.modalDesc": "Conseil : vous pouvez glisser-déposer le nom d'un utilisateur pour l'affecter à une salle de réunion spécifique.", "app.createBreakoutRoom.roomTime": "{0} minutes", @@ -679,11 +700,11 @@ "app.externalVideo.autoPlayWarning": "Jouer la vidéo pour permettre la synchronisation des médias", "app.network.connection.effective.slow": "Nous remarquons des problèmes de connectivité.", "app.network.connection.effective.slow.help": "Plus d'information", - "app.externalVideo.noteLabel": "Remarque : les vidéos externes partagées n'apparaîtront pas dans l'enregistrement. Les URLs YouTube, Vimeo, Instructure Media, Twitch et Daily Motion sont supportées.", + "app.externalVideo.noteLabel": "Remarque : les vidéos externes partagées n'apparaîtront pas dans l'enregistrement. Les URL YouTube, Vimeo, Instructure Media, Twitch, Dailymotion et les URL de fichiers multimédias (par exemple https://example.com/xy.mp4) sont pris en charge.", "app.actionsBar.actionsDropdown.shareExternalVideo": "Partager une vidéo externe", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Arrêter le partage de vidéo externe", "app.iOSWarning.label": "Veuillez mettre à jour vers iOS 12.2 ou supérieur", - "app.legacy.unsupportedBrowser": "Il semblerait que vous utilisiez un navigateur qui n'est pas supporté. Veuillez utiliser {0} or {1} pour un support complet.", + "app.legacy.unsupportedBrowser": "Il semblerait que vous utilisiez un navigateur qui n'est pas supporté. Veuillez utiliser {0} ou {1} pour un support complet.", "app.legacy.upgradeBrowser": "Il semblerait que vous utilisiez une ancienne version d'un navigateur supporté. Veuillez mettre à jour votre navigateur pour un support complet.", "app.legacy.criosBrowser": "Sur iOS, veuillez utiliser Safari pour un support complet." diff --git a/bigbluebutton-html5/private/locales/gl.json b/bigbluebutton-html5/private/locales/gl.json index a37af171bc8b226567a4063b88d6f95b0730e97b..4bc408acd8ddccc6137110f29b896ad40ee2cc4c 100644 --- a/bigbluebutton-html5/private/locales/gl.json +++ b/bigbluebutton-html5/private/locales/gl.json @@ -18,6 +18,7 @@ "app.chat.dropdown.save": "Gardar", "app.chat.label": "Conversa", "app.chat.offline": "Sen conexión", + "app.chat.pollResult": "Resultados da enquisa", "app.chat.emptyLogLabel": "Rexistro da conversa baleiro", "app.chat.clearPublicChatMessage": "A conversa publica foi retirada por un moderador", "app.chat.multi.typing": "Varios usuarios están a escribir", @@ -25,7 +26,7 @@ "app.chat.two.typing": "{0} e {1} están a escribir", "app.captions.label": "Lendas", "app.captions.menu.close": "Pechar", - "app.captions.menu.start": "Iniciar", + "app.captions.menu.start": "Comezar", "app.captions.menu.ariaStart": "Comezar a escribir lendas", "app.captions.menu.ariaStartDesc": "Abre o editor de lendas e pecha a xanela modal", "app.captions.menu.select": "Seleccione idiomas dispoñÃbeis", @@ -44,16 +45,16 @@ "app.captions.pad.ownershipTooltip": "Vai ser asignado coma propietario de {0} lendas", "app.captions.pad.interimResult": "Resultados provisionais", "app.captions.pad.dictationStart": "Iniciar ditado", - "app.captions.pad.dictationStop": "Deter ditado", + "app.captions.pad.dictationStop": "Deixar o ditado", "app.captions.pad.dictationOnDesc": "Activar o recoñecemento de voz", "app.captions.pad.dictationOffDesc": "Desactivar o recoñecemento de voz", "app.note.title": "Notas compartidas", "app.note.label": "Nota", "app.note.hideNoteLabel": "Agochar nota", + "app.note.tipLabel": "Prema Esc para enfocar a barra de ferramentas do editor", "app.user.activityCheck": "Comprobar a actividade do usuario", "app.user.activityCheck.label": "Comprobar se o usuario aÃnda está na xuntanza ({0})", "app.user.activityCheck.check": "Comprobar", - "app.note.tipLabel": "Prema Esc para enfocar a barra de ferramentas do editor", "app.userList.usersTitle": "Usuarios", "app.userList.participantsTitle": "Participantes", "app.userList.messagesTitle": "Mensaxes", @@ -76,7 +77,7 @@ "app.userList.menu.removeConfirmation.label": "Retirar o usuario ({0})", "app.userlist.menu.removeConfirmation.desc": " Impedir que este usuario se reincorpore á sesión.", "app.userList.menu.muteUserAudio.label": "Desactivar o son do usuario", - "app.userList.menu.unmuteUserAudio.label": "Activar o son do usuario", + "app.userList.menu.unmuteUserAudio.label": "Devolverlle o son ao usuario", "app.userList.userAriaLabel": "{0} {1} {2} estado {3}", "app.userList.menu.promoteUser.label": "Promover a moderador", "app.userList.menu.demoteUser.label": "Relegado a espectador", @@ -92,9 +93,11 @@ "app.userList.userOptions.muteAllExceptPresenterLabel": "Silenciar a todos os usuarios agás o presentador", "app.userList.userOptions.muteAllExceptPresenterDesc": "Silenciar a todos os usuarios na xuntanza agás o presentador", "app.userList.userOptions.unmuteAllLabel": "Desactivar o silencio na xuntanza", - "app.userList.userOptions.unmuteAllDesc": "Activar o son na xuntanza", + "app.userList.userOptions.unmuteAllDesc": "Devolverlle o son á xuntanza", "app.userList.userOptions.lockViewersLabel": "Bloquear espectadores", "app.userList.userOptions.lockViewersDesc": "Bloquear certas funcionalidades para os asistentes ao encontro", + "app.userList.userOptions.connectionStatusLabel": "Estado de conexión", + "app.userList.userOptions.connectionStatusDesc": "Ver o estado de conexión dos usuarios", "app.userList.userOptions.disableCam": "As cámaras web dos espectadores están desactivadas", "app.userList.userOptions.disableMic": "Os micrófonos dos espectadores están desactivados", "app.userList.userOptions.disablePrivChat": "A conversa privada está desactivada", @@ -124,12 +127,15 @@ "app.screenshare.genericError": "Erro: produciuse un erro ao compartir a pantalla, ténteo de novo", "app.meeting.ended": "Rematou a sesión", "app.meeting.meetingTimeRemaining": "Tempo restante da xuntanza: {0}", - "app.meeting.meetingTimeHasEnded": "Rematou o tempo, A xuntanza pecharase en breve", + "app.meeting.meetingTimeHasEnded": "Rematou o tempo. A xuntanza pecharase en breve", "app.meeting.endedMessage": "Será reenviado á pantalla de inicio", - "app.meeting.alertMeetingEndsUnderOneMinute": "A xuntanza pecharase nun minuto", - "app.meeting.alertBreakoutEndsUnderOneMinute": "A sala parcial pecharase nun minuto", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "A xuntanza pecharase nun minuto.", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "A xuntanza pecharase en {0} minutos.", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "A sala parcial pecharase en {0} minutos.", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "A sala parcial pecharase nun minuto.", "app.presentation.hide": "Agochar a presentación", "app.presentation.notificationLabel": "Presentación actual", + "app.presentation.downloadLabel": "Descargar", "app.presentation.slideContent": "Contido da diapositiva", "app.presentation.startSlideContent": "Inicio do contido das diapositivas", "app.presentation.endSlideContent": "Fin do contido das diapositivas", @@ -174,9 +180,10 @@ "app.presentationUploder.rejectedError": "O(s) ficheiro(s) seleccionado(s) foi(foron) rexeitado(s). Revise o(os) tipo(s) de ficheiro.", "app.presentationUploder.upload.progress": "Enviando ({0}%)", "app.presentationUploder.upload.413": "O ficheiro é grande de máis, divÃdao en varios ficheiros", - "app.presentationUploder.upload.408": "Solicitar o tempo de espera da testemuña de envÃo.", - "app.presentationUploder.upload.404": "404: testemuña de envÃo non válida", - "app.presentationUploder.upload.401": "Produciuse un fallo na solicitude do tempo de espera da testemuña de envÃo.", + "app.presentationUploder.genericError": "Ouh! algo foi mal…", + "app.presentationUploder.upload.408": "Solicitar o tempo de espera do testemuño de envÃo.", + "app.presentationUploder.upload.404": "404: o testemuño de envÃo non válido", + "app.presentationUploder.upload.401": "Produciuse un fallo na solicitude do tempo de espera do testemuño de envÃo.", "app.presentationUploder.conversion.conversionProcessingSlides": "Procesando páxina {0} de {1}", "app.presentationUploder.conversion.genericConversionStatus": "Convertendo ficheiros…", "app.presentationUploder.conversion.generatingThumbnail": "Xerando miniaturas…", @@ -195,6 +202,13 @@ "app.presentationUploder.tableHeading.filename": "Nome de ficheiro", "app.presentationUploder.tableHeading.options": "Opcións", "app.presentationUploder.tableHeading.status": "Estado", + "app.presentationUploder.uploading": "Enviando {0} {1}", + "app.presentationUploder.uploadStatus": "Completados {0} de {1} envÃos", + "app.presentationUploder.completed": "Completados {0} envÃos", + "app.presentationUploder.item" : "elemento", + "app.presentationUploder.itemPlural" : "elementos", + "app.presentationUploder.clearErrors": "Limpar os erros", + "app.presentationUploder.clearErrorsDesc": "Limpar os envÃos de presentación fallados", "app.poll.pollPaneTitle": "Enquisa", "app.poll.quickPollTitle": "Enquisa rápida", "app.poll.hidePollDesc": "Agochar o panel do menú da enquisa", @@ -240,6 +254,7 @@ "app.connectingMessage": "Conectandose…", "app.waitingMessage": "Desconectado. Tentando volver conectar en {0} segundos…", "app.retryNow": "Volver tentalo agora", + "app.muteWarning.label": "Prema en {0} para silenciarse a vostede mesmo.", "app.navBar.settingsDropdown.optionsLabel": "Opcións", "app.navBar.settingsDropdown.fullscreenLabel": "Poñer a pantalla completa", "app.navBar.settingsDropdown.settingsLabel": "Axustes", @@ -267,7 +282,7 @@ "app.leaveConfirmation.confirmLabel": "Abandonar", "app.leaveConfirmation.confirmDesc": "Desconectarse da xuntanza", "app.endMeeting.title": "Rematar a xuntanza", - "app.endMeeting.description": "Confirma que quere rematar esta sesión?", + "app.endMeeting.description": "Confirma que quere rematar esta xuntanza para todos (todos os usuarios serán desconectados)?", "app.endMeeting.yesLabel": "Si", "app.endMeeting.noLabel": "Non", "app.about.title": "Sobre", @@ -279,7 +294,7 @@ "app.about.dismissDesc": "Pechar a información sobre o cliente", "app.actionsBar.changeStatusLabel": "Cambiar o estado", "app.actionsBar.muteLabel": "Desactivar o son", - "app.actionsBar.unmuteLabel": "Activar o son", + "app.actionsBar.unmuteLabel": "Devolver o son ", "app.actionsBar.camOffLabel": "Cámara apagada", "app.actionsBar.raiseLabel": "Erguer", "app.actionsBar.label": "Barra de accións", @@ -288,10 +303,6 @@ "app.screenshare.screenShareLabel" : "Compartir pantalla", "app.submenu.application.applicationSectionTitle": "Aplicación", "app.submenu.application.animationsLabel": "Animacións", - "app.submenu.application.audioAlertLabel": "Alertas de son para a conversa", - "app.submenu.application.pushAlertLabel": "Alertas visuais para a conversa", - "app.submenu.application.userJoinAudioAlertLabel": "Alertas de son para usuario entrante", - "app.submenu.application.userJoinPushAlertLabel": "Alertas emerxentes para usuario entrante", "app.submenu.application.fontSizeControlLabel": "Tamaño da letra", "app.submenu.application.increaseFontBtnLabel": "Incrementar o tamaño da letra", "app.submenu.application.decreaseFontBtnLabel": "DiminuÃr o tamaño da letra", @@ -299,6 +310,12 @@ "app.submenu.application.languageLabel": "Idioma da aplicación", "app.submenu.application.languageOptionLabel": "Escoller idioma", "app.submenu.application.noLocaleOptionLabel": "Non hai locais activos", + "app.submenu.notification.SectionTitle": "Notificacións", + "app.submenu.notification.Desc": "Definir como e que se lle notificará.", + "app.submenu.notification.audioAlertLabel": "Avisos sonoros", + "app.submenu.notification.pushAlertLabel": "Avisos emerxentes", + "app.submenu.notification.messagesLabel": "Mensaxe de conversa", + "app.submenu.notification.userJoinLabel": "Uniuse un usuario", "app.submenu.audio.micSourceLabel": "Fonte de micrófono", "app.submenu.audio.speakerSourceLabel": "Fonte de altofalante", "app.submenu.audio.streamVolumeLabel": "Volume do fluxo de son", @@ -322,6 +339,10 @@ "app.settings.dataSavingTab.screenShare": "Activar o escritorio compartido", "app.settings.dataSavingTab.description": "Para aforrar largo de banda axuste o que se se está a amosar", "app.settings.save-notification.label": "Gardáronse os axustes", + "app.statusNotifier.lowerHands": "Mans baixadas", + "app.statusNotifier.raisedHandsTitle": "Mans ergueitas", + "app.statusNotifier.raisedHandDesc": "{0} ergueron a man", + "app.statusNotifier.and": "e", "app.switch.onLabel": "Aceso", "app.switch.offLabel": "Apagado", "app.talkingIndicator.ariaMuteDesc" : "Seleccione para silenciar o usuario", @@ -373,7 +394,7 @@ "app.audioNotification.audioFailedError1001": "WebSocket desconectado (error 1001)", "app.audioNotification.audioFailedError1002": "Non foi posÃbel facer unha conexión WebSocket (erro 1002)", "app.audioNotification.audioFailedError1003": "A versión do navegador non é compatÃbel (erro 1003)", - "app.audioNotification.audioFailedError1004": "Produciuse un fallo na chamada (razón={0}) (erro 1004)", + "app.audioNotification.audioFailedError1004": "Produciuse un fallo na chamada (motivo={0}) (erro 1004)", "app.audioNotification.audioFailedError1005": "A chamada rematou inesperadamente (erro 1005)", "app.audioNotification.audioFailedError1006": "Rematou o tempo de espera da chamada (erro 1006)", "app.audioNotification.audioFailedError1007": "Produciuse un fallo de conexión (erro ICE 1007)", @@ -412,7 +433,7 @@ "app.audioModal.echoTestTitle": "Esta é unha proba de eco privada. Diga unhas palabras. Escoitou o son?", "app.audioModal.settingsTitle": "Cambiar a súa configuración de son", "app.audioModal.helpTitle": "Houbo un problema cos seus dispositivos multimedia", - "app.audioModal.helpText": "Deu permiso para acceder ao seu micrófono? Teña en conta que deberÃa aparecer un diálogo cando tente unirse ao son, solicitando os permisos do seu dispositivo multimedia, acépteos para unirse a conferencia de son. Se non é asÃ, tente cambiar os permisos do micrófono na configuración do seu navegador.", + "app.audioModal.helpText": "Deu permiso para acceder ao seu micrófono? Teña en conta que deberÃa aparecer un diálogo cando tente unirse ao son, solicitando os permisos do seu dispositivo multimedia, acépteos para unirse á conferencia de son. Se non é asÃ, tente cambiar os permisos do micrófono na configuración do seu navegador.", "app.audioModal.help.noSSL": "Esta páxina non é segura. Para poder acceder ao micrófono a páxina ten que ser servida mediante HTTPS. Contacte co administrador do servidor.", "app.audioModal.help.macNotAllowed": "Parece que as preferencias do teu sistema Mac están a bloquear o acceso ao mÃcrofono. Abra Preferencias do sistema > Seguridade e privacidade > Privacidade > Micrófono, e verifique que o navegador que está a usar está marcado.", "app.audioModal.audioDialTitle": "Unirse usando o seu teléfono", @@ -422,12 +443,12 @@ "app.audioModal.playAudio": "Reproducir son", "app.audioModal.playAudio.arialabel" : "Reproducir son", "app.audioDial.tipIndicator": "Consello", - "app.audioDial.tipMessage": "Prema a tecla «0» no seu teléfono para silenciar/activar o seu propio son.", + "app.audioDial.tipMessage": "Prema a tecla «0» no seu teléfono para silenciar/devolver o seu propio son.", "app.audioModal.connecting": "Conectando", "app.audioModal.connectingEchoTest": "Conectando coa proba de eco", "app.audioManager.joinedAudio": "Vostede uniuse á conferencia de son", "app.audioManager.joinedEcho": "Vostede uniuse á proba de eco", - "app.audioManager.leftAudio": "Vostede abandonou á conferencia de son", + "app.audioManager.leftAudio": "Vostede abandonou a conferencia de son", "app.audioManager.reconnectingAudio": "Tentando volver conectar o son", "app.audioManager.genericError": "Erro: produciuse un erro, tenteo de novo", "app.audioManager.connectionError": "Erro: Produciuse un erro de conexión", @@ -440,7 +461,7 @@ "app.audio.playSoundLabel": "Reproducir son", "app.audio.backLabel": "Atrás", "app.audio.audioSettings.titleLabel": "Escolla os seus axustes do son", - "app.audio.audioSettings.descriptionLabel": "Teña en conta que aparecerá un diálogo no navegador que requira que acepte compartir o seu micrófono", + "app.audio.audioSettings.descriptionLabel": "Teña en conta que aparecerá un diálogo no navegador que lle requerira que acepte compartir o seu micrófono", "app.audio.audioSettings.microphoneSourceLabel": "Fonte de micrófono", "app.audio.audioSettings.speakerSourceLabel": "Fonte de altofalante", "app.audio.audioSettings.microphoneStreamLabel": "O seu volume do fluxo de son", @@ -454,7 +475,7 @@ "app.meeting.logout.duplicateUserEjectReason": "Usuario duplicado tentando unirse á xuntanza", "app.meeting.logout.permissionEjectReason": "Expulsado por violación de permiso", "app.meeting.logout.ejectedFromMeeting": "Vostede foi retirado/a da xuntanza", - "app.meeting.logout.validateTokenFailedEjectReason": "Produciuse un erro ao validar a testemuña de autorización", + "app.meeting.logout.validateTokenFailedEjectReason": "Produciuse un erro ao validar o testemuño de autorización", "app.meeting.logout.userInactivityEjectReason": "Usuario inactivo durante demasiado tempo", "app.meeting-ended.rating.legendLabel": "Valoración de comentarios", "app.meeting-ended.rating.starLabel": "Estrela", @@ -486,6 +507,8 @@ "app.userList.guest.pendingGuestUsers": "{0} convidados pendentes", "app.userList.guest.pendingGuestAlert": "Uniuse á sesión e está agardando a súa aprobación.", "app.userList.guest.rememberChoice": "Lembrar a escolla", + "app.userList.guest.acceptLabel": "Aceptar", + "app.userList.guest.denyLabel": "Denegar", "app.user-info.title": "Atopar directorio", "app.toast.breakoutRoomEnded": "A sala parcial rematou. Volva incorporarse ao son.", "app.toast.chat.public": "Nova mensaxe na conversa pública", @@ -500,6 +523,7 @@ "app.notification.recordingPaused": "Xa non se está a gravar esta sesión", "app.notification.recordingAriaLabel": "Tempo de gravación", "app.notification.userJoinPushAlert": "{0} incorporouse á sesión", + "app.submenu.notification.raiseHandLabel": "Erguer a man", "app.shortcut-help.title": "Atallos de teclado", "app.shortcut-help.accessKeyNotAvailable": "Teclas de acceso non dispoñÃbeis", "app.shortcut-help.comboLabel": "Combinación", @@ -508,7 +532,7 @@ "app.shortcut-help.closeDesc": " Pecha a xanela modal de atallos do teclado", "app.shortcut-help.openOptions": "Abrir opcións", "app.shortcut-help.toggleUserList": "Alternar a lista de usuarios", - "app.shortcut-help.toggleMute": "Silenciar/Activar son", + "app.shortcut-help.toggleMute": "Silenciar/Devolver o son", "app.shortcut-help.togglePublicChat": "Alternar a conversa pública (a lista de usuarios debe estar aberta)", "app.shortcut-help.hidePrivateChat": "Agochar a conversa privada", "app.shortcut-help.closePrivateChat": "Pechar a conversa privada", @@ -533,17 +557,30 @@ "app.lock-viewers.button.cancel": "Cancelar", "app.lock-viewers.locked": "Bloqueado", "app.lock-viewers.unlocked": "Desbloqueado", - "app.recording.startTitle": "Iniciar gravación", - "app.recording.stopTitle": "Pausar gravación", - "app.recording.resumeTitle": "Continuar gravación", + "app.connection-status.ariaTitle": "Estado de conexión modal", + "app.connection-status.title": "Estado de conexión", + "app.connection-status.description": "Ver o estado de conexión dos usuarios", + "app.connection-status.empty": "Non hai ningún problema de conectividade informado ata o de agora", + "app.connection-status.more": "máis", + "app.connection-status.offline": "sen conexión", + "app.recording.startTitle": "Iniciar a gravación", + "app.recording.stopTitle": "Pausar a gravación", + "app.recording.resumeTitle": "Continuar coa gravación", "app.recording.startDescription": "Máis tarde pode usar o botón de gravación para deter a gravación.", "app.recording.stopDescription": "Confirma que quere deter a gravación? Pode continuala premendo de novo o botón de gravación.", "app.videoPreview.cameraLabel": "Cámara web", "app.videoPreview.profileLabel": "Calidade", + "app.videoPreview.quality.low": "Baixa", + "app.videoPreview.quality.medium": "Media", + "app.videoPreview.quality.high": "Alta", + "app.videoPreview.quality.hd": "Alta definición", "app.videoPreview.cancelLabel": "Cancelar", "app.videoPreview.closeLabel": "Pechar", "app.videoPreview.findingWebcamsLabel": "Buscando cámaras web", "app.videoPreview.startSharingLabel": "Comezar a compartir", + "app.videoPreview.stopSharingLabel": "Deixar de compartir", + "app.videoPreview.stopSharingAllLabel": "Deixalo todo", + "app.videoPreview.sharedCameraLabel": "Esta cámara xa está a ser compartida", "app.videoPreview.webcamOptionLabel": "Seleccionar a cámara web", "app.videoPreview.webcamPreviewLabel": "Vista preliminar de cámara web", "app.videoPreview.webcamSettingsTitle": "Axustes da cámara web", @@ -558,7 +595,7 @@ "app.video.notFoundError": "Non se atopou a cámara web. Asegúrese de que estea conectada", "app.video.notAllowed": "Fallo o permiso para a cámara web compartida, asegúrese de que os permisos do seu navegador son correctos", "app.video.notSupportedError": "Só é posÃbel compartir cámaras web de fontes seguras, asegúrese de que o certificado SSL sexa valido", - "app.video.notReadableError": "Non se puido obter vÃdeo de webcam. Asegurate de que ningunha outra aplicación estea utilizandola.\n\nNon foi posÃbel obter o vÃdeo da cámara web. Asegurese de que outro programa non estea a usar a cámara web", + "app.video.notReadableError": "Non se puido obter vÃdeo de webcam. Asegurate de que ningunha outra aplicación estea utilizandola. Non foi posÃbel obter o vÃdeo da cámara web. Asegurese de que outro programa non estea a usar a cámara web", "app.video.mediaFlowTimeout1020": "Os recursos multimedia non foron quen de acadar o servidor (erro 1020)", "app.video.suggestWebcamLock": "Forzar os axustes de bloqueo para as cámaras web dos espectadores?", "app.video.suggestWebcamLockReason": "(isto mellorará a estabilidade da xuntanza)", @@ -573,19 +610,8 @@ "app.video.videoMenuDesc": "Abrir o menú despregable de vÃdeo", "app.video.chromeExtensionError": "Debe instalar", "app.video.chromeExtensionErrorLink": "esta extensión de Chrome", - "app.video.stats.title": "EstatÃsticas de conexión", - "app.video.stats.packetsReceived": "Paquetes recibidos", - "app.video.stats.packetsSent": "Paquetes enviados", - "app.video.stats.packetsLost": "Paquetes perdidos", - "app.video.stats.bitrate": "Taxa de bits", - "app.video.stats.lostPercentage": "Porcentaxe total de perda", - "app.video.stats.lostRecentPercentage": "Porcentaxe de perda recente", - "app.video.stats.dimensions": "Dimensións", - "app.video.stats.codec": "Códec", - "app.video.stats.decodeDelay": "Demora de decodificación", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Uso de codificador", - "app.video.stats.currentDelay": "Demora actual", + "app.video.pagination.prevPage": "Ver os vÃdeos anteriores", + "app.video.pagination.nextPage": "Ver os seguintes vÃdeos", "app.fullscreenButton.label": "Poñer {0} a pantalla completa", "app.deskshare.iceConnectionStateError": "Produciuse un fallo de conexión ao compartir a pantalla (erro ICE 1108)", "app.sfu.mediaServerConnectionError2000": "Non foi posÃbel conectar co servidor multimedia (erro 2000)", @@ -599,6 +625,7 @@ "app.sfu.noAvailableCodec2203": "O servidor non atopou un códec axeitado (erro 2203)", "app.meeting.endNotification.ok.label": "Aceptar", "app.whiteboard.annotations.poll": "Os resultados da enquisa foron publicados", + "app.whiteboard.annotations.pollResult": "Resultados da enquisa", "app.whiteboard.toolbar.tools": "Ferramentas", "app.whiteboard.toolbar.tools.hand": "Panorama", "app.whiteboard.toolbar.tools.pencil": "Lapis", @@ -679,9 +706,9 @@ "app.externalVideo.autoPlayWarning": "Reproducir o vÃdeo para activar a sincronización de recursos multimedia", "app.network.connection.effective.slow": "Estamos a detectar problemas de conectividade.", "app.network.connection.effective.slow.help": "Máis información", - "app.externalVideo.noteLabel": "Nota: os vÃdeos externos compartidos non aparecerán na gravación. AdmÃtense os URL de YouTube, Vimeo, Instructure Media, Twitch e Daily Motion.", + "app.externalVideo.noteLabel": "Nota: os vÃdeos externos compartidos non aparecerán na gravación. AdmÃtense os URL de YouTube, Vimeo, Instructure Media, Twitch, Daily Motion e ficheiros multimedia (p. ex. https://exemplo.com/xy.mp4).", "app.actionsBar.actionsDropdown.shareExternalVideo": "Compartir un vÃdeo externo", - "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Deter a compartición do vÃdeo externo", + "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Deixar de compartir o vÃdeo externo", "app.iOSWarning.label": "Actualice a iOS 12.2 ou superior", "app.legacy.unsupportedBrowser": "Parece que estás a usar un navegador que non é compatÃbel. Utilice {0} ou {1} para obter unha compatibilidade completa.", "app.legacy.upgradeBrowser": "Parece que estás a usar unha versión antiga dun navegador compatÃbel. Actualice o navegador para obter unha compatibilidade completa. ", diff --git a/bigbluebutton-html5/private/locales/he.json b/bigbluebutton-html5/private/locales/he.json index a3d1ce710807388f2645451cc417a8722d93a332..41bcdd397616d00df817326797e01d600eec2141 100644 --- a/bigbluebutton-html5/private/locales/he.json +++ b/bigbluebutton-html5/private/locales/he.json @@ -50,10 +50,10 @@ "app.note.title": "×¤× ×§×¡ משותף", "app.note.label": "פתקית", "app.note.hideNoteLabel": "הסתר פתקית", + "app.note.tipLabel": "לחץ Esc למעבר לסרגל הכלי×", "app.user.activityCheck": "בדיקת ×–×ž×™× ×•×ª משתמש", "app.user.activityCheck.label": "בדוק ×× ×”×ž×©×ª×ž×© עדיין במפגש ({0})", "app.user.activityCheck.check": "בדוק", - "app.note.tipLabel": "לחץ Esc למעבר לסרגל הכלי×", "app.userList.usersTitle": "משתתפי×", "app.userList.participantsTitle": "משתתפי×", "app.userList.messagesTitle": "הודעות", @@ -121,8 +121,6 @@ "app.meeting.meetingTimeRemaining": "זמן × ×•×ª×¨ למפגש: {0}", "app.meeting.meetingTimeHasEnded": "המפגש ×”×¡×ª×™×™× ×•×™×¡×’×¨ בקרוב", "app.meeting.endedMessage": "×ª×•×¤× ×” למסך הבית", - "app.meeting.alertMeetingEndsUnderOneMinute": "המפגש ×™×¡×ª×™×™× ×‘×¢×•×“ כדקה.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Breakout is closing in a minute.", "app.presentation.hide": "Hide presentation", "app.presentation.notificationLabel": "מצגת × ×•×›×—×™×ª", "app.presentation.slideContent": "שקופיות", @@ -259,7 +257,6 @@ "app.leaveConfirmation.confirmLabel": "צ×", "app.leaveConfirmation.confirmDesc": "×ž× ×ª×§ ×ותך מהמפגש", "app.endMeeting.title": "סגור כיתה", - "app.endMeeting.description": "×תה בטוח ×©×‘×¨×¦×•× ×š ×œ×¡×™×™× ×ת המפגש?", "app.endMeeting.yesLabel": "כן", "app.endMeeting.noLabel": "ל×", "app.about.title": "×ודות", @@ -280,10 +277,6 @@ "app.screenshare.screenShareLabel" : "שיתוף מסך", "app.submenu.application.applicationSectionTitle": "×פליקציה", "app.submenu.application.animationsLabel": "×× ×™×ž×¦×™×”", - "app.submenu.application.audioAlertLabel": "התר×ות קול לצ'×ט", - "app.submenu.application.pushAlertLabel": "התר×ות קופצות לצ'×ט", - "app.submenu.application.userJoinAudioAlertLabel": "התר×ות קול להצטרפות משתתף", - "app.submenu.application.userJoinPushAlertLabel": "התר×ות קול להצטרפות משתתף", "app.submenu.application.fontSizeControlLabel": "גודל ×¤×•× ×˜", "app.submenu.application.increaseFontBtnLabel": "הקטן גודל ×¤×•× ×˜", "app.submenu.application.decreaseFontBtnLabel": "הגדל גודל ×¤×•× ×˜", @@ -565,19 +558,6 @@ "app.video.videoMenuDesc": "Open video menu dropdown", "app.video.chromeExtensionError": "You must install", "app.video.chromeExtensionErrorLink": "this Chrome extension", - "app.video.stats.title": "Connection Stats", - "app.video.stats.packetsReceived": "Packets received", - "app.video.stats.packetsSent": "Packets sent", - "app.video.stats.packetsLost": "Packets lost", - "app.video.stats.bitrate": "Bitrate", - "app.video.stats.lostPercentage": "Total percentage lost", - "app.video.stats.lostRecentPercentage": "Recent percentage lost", - "app.video.stats.dimensions": "Dimensions", - "app.video.stats.codec": "Codec", - "app.video.stats.decodeDelay": "Decode delay", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Encode usage", - "app.video.stats.currentDelay": "Current delay", "app.fullscreenButton.label": "Make {0} fullscreen", "app.deskshare.iceConnectionStateError": "Error 1108: ICE connection failed when sharing screen", "app.sfu.mediaServerConnectionError2000": "Error 2000: Unable to connect to media server", @@ -590,7 +570,6 @@ "app.sfu.invalidSdp2202":"Error 2202: Client generated an invalid SDP", "app.sfu.noAvailableCodec2203": "Error 2203: Server could not find an appropriate codec", "app.meeting.endNotification.ok.label": "×ישור", - "app.whiteboard.annotations.poll": "תוצ×ות סקר פורסמו", "app.whiteboard.toolbar.tools": "כלי×", "app.whiteboard.toolbar.tools.hand": "×”×–×–×”", "app.whiteboard.toolbar.tools.pencil": "עפרון", @@ -671,7 +650,6 @@ "app.externalVideo.autoPlayWarning": "Play the video to enable media synchronization", "app.network.connection.effective.slow": "We're noticing connectivity issues.", "app.network.connection.effective.slow.help": "More information", - "app.externalVideo.noteLabel": "Note: Shared external videos will not appear in the recording. YouTube, Vimeo, Instructure Media, Twitch and Daily Motion URLs are supported.", "app.actionsBar.actionsDropdown.shareExternalVideo": "Share an external video", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Stop sharing external video", "app.iOSWarning.label": "Please upgrade to iOS 12.2 or higher", diff --git a/bigbluebutton-html5/private/locales/hi_IN.json b/bigbluebutton-html5/private/locales/hi_IN.json index fd2dd983862dac36ddf332b5d85f3794707a62cd..ff57b408b90d1535442c904edb919965daaef0a9 100644 --- a/bigbluebutton-html5/private/locales/hi_IN.json +++ b/bigbluebutton-html5/private/locales/hi_IN.json @@ -49,10 +49,10 @@ "app.note.title": "साà¤à¤¾ किठगठनोटà¥à¤¸", "app.note.label": "धà¥à¤¯à¤¾à¤¨ दें", "app.note.hideNoteLabel": "नोट छिपाà¤à¤‚", + "app.note.tipLabel": "संपादक टूलबार पर धà¥à¤¯à¤¾à¤¨ केंदà¥à¤°à¤¿à¤¤ करने के लिठEsc दबाà¤à¤‚", "app.user.activityCheck": "उपयोगकरà¥à¤¤à¤¾ गतिविधि की जाà¤à¤š करें", "app.user.activityCheck.label": "जांचें कि कà¥à¤¯à¤¾ उपयोगकरà¥à¤¤à¤¾ अà¤à¥€ à¤à¥€ मिल रहा है ({0})", "app.user.activityCheck.check": "चेक", - "app.note.tipLabel": "संपादक टूलबार पर धà¥à¤¯à¤¾à¤¨ केंदà¥à¤°à¤¿à¤¤ करने के लिठEsc दबाà¤à¤‚", "app.userList.usersTitle": "उपयोगकरà¥à¤¤à¤¾à¤“ं", "app.userList.participantsTitle": "पà¥à¤°à¤¤à¤¿à¤à¤¾à¤—ियों", "app.userList.messagesTitle": "संदेश", @@ -101,8 +101,6 @@ "app.meeting.meetingTimeRemaining": "बैठक का समय शेष: {0}", "app.meeting.meetingTimeHasEnded": "समय समापà¥à¤¤ हà¥à¤†à¥¤ बैठक जलà¥à¤¦ ही बंद हो जाà¤à¤—ी", "app.meeting.endedMessage": "You will be forwarded back to the home screen", - "app.meeting.alertMeetingEndsUnderOneMinute": "बैठक à¤à¤• मिनट में बंद हो रही है।", - "app.meeting.alertBreakoutEndsUnderOneMinute": "बà¥à¤°à¥‡à¤•आउट à¤à¤• मिनट में बंद हो रहा है।", "app.presentation.hide": "पà¥à¤°à¤¸à¥à¤¤à¥à¤¤à¤¿ छिपाà¤à¤", "app.presentation.slideContent": "सà¥à¤²à¤¾à¤‡à¤¡ सामगà¥à¤°à¥€", "app.presentation.startSlideContent": "सà¥à¤²à¤¾à¤‡à¤¡ सामगà¥à¤°à¥€ पà¥à¤°à¤¾à¤°à¤‚à¤", @@ -213,7 +211,6 @@ "app.leaveConfirmation.confirmLabel": "छोड़ना", "app.leaveConfirmation.confirmDesc": "आपको मीटिंग से बाहर कर देता है", "app.endMeeting.title": "मीटिंग खतà¥à¤®", - "app.endMeeting.description": "कà¥à¤¯à¤¾ आप वाकई इस सतà¥à¤° को समापà¥à¤¤ करना चाहते हैं?", "app.endMeeting.yesLabel": "हाà¤", "app.endMeeting.noLabel": "नहीं", "app.about.title": "के बारे में", @@ -234,8 +231,6 @@ "app.screenshare.screenShareLabel" : "सà¥à¤•à¥à¤°à¥€à¤¨ शेयर", "app.submenu.application.applicationSectionTitle": "आवेदन", "app.submenu.application.animationsLabel": "à¤à¤¨à¤¿à¤®à¥‡à¤¶à¤¨", - "app.submenu.application.audioAlertLabel": "चैट के लिठऑडियो अलरà¥à¤Ÿ", - "app.submenu.application.pushAlertLabel": "चैट के लिठपॉपअप अलरà¥à¤Ÿ", "app.submenu.application.fontSizeControlLabel": "फ़ॉनà¥à¤Ÿ आकार", "app.submenu.application.increaseFontBtnLabel": "à¤à¤ªà¥à¤²à¤¿à¤•ेशन फ़ॉनà¥à¤Ÿ आकार बढ़ाà¤à¤‚", "app.submenu.application.decreaseFontBtnLabel": "à¤à¤ªà¥à¤²à¤¿à¤•ेशन फ़ॉनà¥à¤Ÿ आकार घटाà¤à¤‚", @@ -337,7 +332,7 @@ "app.audioDial.audioDialDescription": "डायल", "app.audioDial.audioDialConfrenceText": "और समà¥à¤®à¥‡à¤²à¤¨ पिन नंबर दरà¥à¤œ करें:", "app.audioModal.connecting": "कनेकà¥à¤Ÿ", - "app.audioModal.connectingEchoTest": "कनेकà¥à¤Ÿ\nइको टेसà¥à¤Ÿ से जà¥à¤¡à¤¼à¤¨à¤¾", + "app.audioModal.connectingEchoTest": "कनेकà¥à¤Ÿ इको टेसà¥à¤Ÿ से जà¥à¤¡à¤¼à¤¨à¤¾", "app.audioManager.joinedAudio": "आप ऑडियो कॉनà¥à¤«à¥à¤°à¥‡à¤‚स में शामिल हà¥à¤ हैं", "app.audioManager.joinedEcho": "आप इको टेसà¥à¤Ÿ में शामिल हो गठहैं", "app.audioManager.leftAudio": "आपने ऑडियो कॉनà¥à¤«à¥à¤°à¥‡à¤‚स छोड़ दी है", @@ -450,19 +445,6 @@ "app.video.videoMenuDesc": "खà¥à¤²à¤¾ वीडियो मेनू डà¥à¤°à¥‰à¤ªà¤¡à¤¾à¤‰à¤¨", "app.video.chromeExtensionError": "आपको सà¥à¤¥à¤¾à¤ªà¤¿à¤¤ करना होगा", "app.video.chromeExtensionErrorLink": "यह कà¥à¤°à¥‹à¤® à¤à¤•à¥à¤¸à¤Ÿà¥‡à¤‚शन है", - "app.video.stats.title": "कनेकà¥à¤¶à¤¨ आà¤à¤•ड़े", - "app.video.stats.packetsReceived": "पैकेट मिले", - "app.video.stats.packetsSent": "पैकेट à¤à¥‡à¤œà¥‡ गà¤", - "app.video.stats.packetsLost": "पैकेट खो गà¤", - "app.video.stats.bitrate": "बिटरेट", - "app.video.stats.lostPercentage": "कà¥à¤² पà¥à¤°à¤¤à¤¿à¤¶à¤¤ खो गया", - "app.video.stats.lostRecentPercentage": "हाल का पà¥à¤°à¤¤à¤¿à¤¶à¤¤ खो गया", - "app.video.stats.dimensions": "आयाम", - "app.video.stats.codec": "कोडेक", - "app.video.stats.decodeDelay": "देरी हो गई", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "उपयोग को à¤à¤¨à¤•ोड करें", - "app.video.stats.currentDelay": "वरà¥à¤¤à¤®à¤¾à¤¨ देरी", "app.fullscreenButton.label": "फà¥à¤²à¤¸à¥à¤•à¥à¤°à¥€à¤¨ बनाà¤à¤‚ {0}", "app.meeting.endNotification.ok.label": "ठीक", "app.whiteboard.toolbar.tools": "उपकरण", diff --git a/bigbluebutton-html5/private/locales/hr.json b/bigbluebutton-html5/private/locales/hr.json index 512948e7ad4d1f281cf7bf66bb8f066a7067f3df..594d9baeb6acb705f3652684f3bc84a200a34e96 100644 --- a/bigbluebutton-html5/private/locales/hr.json +++ b/bigbluebutton-html5/private/locales/hr.json @@ -84,7 +84,6 @@ "app.media.screenshare.autoplayBlockedDesc": "Trebamo vaÅ¡u dozvolu kako bi vam podijelili zaslon prezentera.", "app.media.screenshare.autoplayAllowLabel": "Prikaži dijeljeni zaslon", "app.meeting.ended": "Ova sesija je zavrÅ¡ila", - "app.meeting.alertMeetingEndsUnderOneMinute": "Sesija se zatvara za minutu.", "app.presentation.hide": "Skrij prezentaciju", "app.presentation.notificationLabel": "TrenutaÄna prezentacija", "app.presentation.slideContent": "Sadržaj slajda", @@ -151,7 +150,6 @@ "app.navBar.recording.off": "Bez snimanja", "app.leaveConfirmation.confirmLabel": "Napusti", "app.endMeeting.title": "ZavrÅ¡i sesiju", - "app.endMeeting.description": "Jeste li sigurni da želite zavrÅ¡iti ovu sesiju?", "app.endMeeting.yesLabel": "Da", "app.endMeeting.noLabel": "Ne", "app.about.confirmLabel": "U redu", @@ -250,9 +248,6 @@ "app.video.videoButtonDesc": "Podijeli web-kameru", "app.video.videoMenu": "Video izbornik", "app.video.chromeExtensionError": "Morate instalirati", - "app.video.stats.dimensions": "Dimenzije", - "app.video.stats.codec": "Kodek", - "app.video.stats.rtt": "RTT", "app.meeting.endNotification.ok.label": "U redu", "app.whiteboard.toolbar.tools": "Alati", "app.whiteboard.toolbar.tools.pencil": "Olovka", diff --git a/bigbluebutton-html5/private/locales/hu_HU.json b/bigbluebutton-html5/private/locales/hu_HU.json index 7036d814344eba524c988598cb49b3dec2881f52..a3bcb10c2a1574765ef3bcfc375a4442b55721db 100644 --- a/bigbluebutton-html5/private/locales/hu_HU.json +++ b/bigbluebutton-html5/private/locales/hu_HU.json @@ -18,6 +18,7 @@ "app.chat.dropdown.save": "Mentés", "app.chat.label": "Üzenetek", "app.chat.offline": "Offline", + "app.chat.pollResult": "Szavazás eredménye", "app.chat.emptyLogLabel": "Az üzenetek naplója üres", "app.chat.clearPublicChatMessage": "A nyilvános beszélgetés elÅ‘zményeit csak moderátor törölheti", "app.chat.multi.typing": "Több felhasználó gépel", @@ -50,10 +51,10 @@ "app.note.title": "Megosztott jegyzetek", "app.note.label": "Jegyzetek", "app.note.hideNoteLabel": "Jegyzet elrejtése", + "app.note.tipLabel": "Nyomj Esc-et a szerkesztÅ‘ eszköztárra fokuszálásához", "app.user.activityCheck": "Felhasználói aktivitás ellenÅ‘rzése", "app.user.activityCheck.label": "EllenÅ‘rzi, hogy a felhasználó még az elÅ‘adás résztvevÅ‘je-e ({0})", "app.user.activityCheck.check": "EllenÅ‘rzés", - "app.note.tipLabel": "Nyomj Esc-et a szerkesztÅ‘ eszköztárra fokuszálásához", "app.userList.usersTitle": "Felhasználók", "app.userList.participantsTitle": "RésztvevÅ‘k", "app.userList.messagesTitle": "Üzenetek", @@ -95,6 +96,8 @@ "app.userList.userOptions.unmuteAllDesc": "Az elÅ‘adás hangosÃtása", "app.userList.userOptions.lockViewersLabel": "RésztvevÅ‘k zárolása", "app.userList.userOptions.lockViewersDesc": "Az elÅ‘adás résztvevÅ‘inek számos funkció zárolása", + "app.userList.userOptions.connectionStatusLabel": "Kapcsolat állapota", + "app.userList.userOptions.connectionStatusDesc": "Felhasználók kapcsolódási állapotának megjelenÃtése ", "app.userList.userOptions.disableCam": "A résztvevÅ‘k webkamerája le van tiltva", "app.userList.userOptions.disableMic": "A résztvevÅ‘k mikrofonja le van tiltva", "app.userList.userOptions.disablePrivChat": "A privát üzenetek le van tiltva", @@ -126,10 +129,13 @@ "app.meeting.meetingTimeRemaining": "Az elÅ‘adásból hátralévÅ‘ idÅ‘: {0}", "app.meeting.meetingTimeHasEnded": "Az idÅ‘ lejárt. Az elÅ‘adás hamarosan véget ér", "app.meeting.endedMessage": "VisszairányÃtunk a kezdÅ‘képernyÅ‘re", - "app.meeting.alertMeetingEndsUnderOneMinute": "Az elÅ‘adás egy perc múlva bezárul.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "A csapatszoba egy perc múlva bezárul.", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "A megbeszélés egy perc múlva véget ér.", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "A megbeszélés {0} percen belül véget ér.", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "A csapatszoba {0} percen belül véget ér.", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "A csapatszoba 1 percen belül véget ér.", "app.presentation.hide": "Prezentáció elrejtése", "app.presentation.notificationLabel": "Jelenlegi prezentáció", + "app.presentation.downloadLabel": "Letöltés", "app.presentation.slideContent": "Diatartalom", "app.presentation.startSlideContent": "Diatartalom indÃtása", "app.presentation.endSlideContent": "Diatartalom befejezése", @@ -174,6 +180,7 @@ "app.presentationUploder.rejectedError": "A kiválasztott fájl(oka)t visszautasÃtottuk. Kérjük, ellenÅ‘rizd a fájl(ok) tÃpusát.", "app.presentationUploder.upload.progress": "({0}%) feltöltve", "app.presentationUploder.upload.413": "A fájl túl nagy. Kérjük, szedd szét több darabra.", + "app.presentationUploder.genericError": "Hoppá, valami hiba történt ...", "app.presentationUploder.upload.408": "Feltöltési token kérés idÅ‘túllépése.", "app.presentationUploder.upload.404": "404: Érvénytelen feltöltési token ", "app.presentationUploder.upload.401": "A prezentáció feltöltési tokenjének kérése sikertelen.", @@ -195,6 +202,13 @@ "app.presentationUploder.tableHeading.filename": "Fájlnév", "app.presentationUploder.tableHeading.options": "BeállÃtások", "app.presentationUploder.tableHeading.status": "Ãllapot", + "app.presentationUploder.uploading": "{0} {1} feltöltése", + "app.presentationUploder.uploadStatus": "{0} / {1} feltöltés befejezÅ‘dött", + "app.presentationUploder.completed": "{0} feltöltés befejezÅ‘dött", + "app.presentationUploder.item" : "elem", + "app.presentationUploder.itemPlural" : "elemek", + "app.presentationUploder.clearErrors": "Hibák törlése", + "app.presentationUploder.clearErrorsDesc": "Hibás feltöltések törlése", "app.poll.pollPaneTitle": "Szavazás", "app.poll.quickPollTitle": "Gyorsszavazás", "app.poll.hidePollDesc": "Szavazásmenü-panel elrejtése", @@ -240,6 +254,7 @@ "app.connectingMessage": "Kapcsolódás ...", "app.waitingMessage": "Szétkapcsolódtunk. {0} másodperc múlva próbálunk újra csatlakozni ...", "app.retryNow": "Most próbáld újra", + "app.muteWarning.label": "{0} kattints az elnémÃtásodhoz.", "app.navBar.settingsDropdown.optionsLabel": "BeállÃtások", "app.navBar.settingsDropdown.fullscreenLabel": "Teljes képernyÅ‘", "app.navBar.settingsDropdown.settingsLabel": "BeállÃtások", @@ -267,7 +282,7 @@ "app.leaveConfirmation.confirmLabel": "Kilépés", "app.leaveConfirmation.confirmDesc": "Kiléptél az elÅ‘adásból", "app.endMeeting.title": "ElÅ‘adás befejezése", - "app.endMeeting.description": "Biztos, hogy bezárja ezt a munkamenetet?", + "app.endMeeting.description": "Biztos, hogy befejezed a megbeszélést mindenki számára (az összes felhasználó kapcsolata megszűnik)?", "app.endMeeting.yesLabel": "Igen", "app.endMeeting.noLabel": "Nem", "app.about.title": "Névjegy", @@ -288,10 +303,6 @@ "app.screenshare.screenShareLabel" : "KépernyÅ‘megosztás", "app.submenu.application.applicationSectionTitle": "Alkalmazás", "app.submenu.application.animationsLabel": "Animációk", - "app.submenu.application.audioAlertLabel": "ÉrtesÃtési hang", - "app.submenu.application.pushAlertLabel": "Felugró értesÃtés", - "app.submenu.application.userJoinAudioAlertLabel": "ÉrtesÃtési hang csatlakozáskor", - "app.submenu.application.userJoinPushAlertLabel": "Felugró értesÃtés csatlakozáskor", "app.submenu.application.fontSizeControlLabel": "Betűméter", "app.submenu.application.increaseFontBtnLabel": "Az alkalmazás betűméretének csökkentése", "app.submenu.application.decreaseFontBtnLabel": "Az alkalmazás betűméretének növelése", @@ -299,6 +310,12 @@ "app.submenu.application.languageLabel": "Az alkalmazás nyelve", "app.submenu.application.languageOptionLabel": "Válassz nyelvet", "app.submenu.application.noLocaleOptionLabel": "Egy aktÃv fordÃtás sincs", + "app.submenu.notification.SectionTitle": "ÉrtesÃtések", + "app.submenu.notification.Desc": "Add meg, mirÅ‘l és hogyan kapj értesÃtéseket.", + "app.submenu.notification.audioAlertLabel": "HangértesÃtés", + "app.submenu.notification.pushAlertLabel": "Felugró értesÃtése", + "app.submenu.notification.messagesLabel": "Üzenet", + "app.submenu.notification.userJoinLabel": "Felhasználó csatlakozás", "app.submenu.audio.micSourceLabel": "Mikrofon forrása", "app.submenu.audio.speakerSourceLabel": "Hangszóró forrása", "app.submenu.audio.streamVolumeLabel": "A hangstream hangerÅ‘d", @@ -322,6 +339,10 @@ "app.settings.dataSavingTab.screenShare": "KépernyÅ‘megosztás engedélyezése", "app.settings.dataSavingTab.description": "IgazÃtsd a sávszélességedhez, hogy mi jelenhessen meg a képernyÅ‘dön.", "app.settings.save-notification.label": "A beállÃtásokat mentettük", + "app.statusNotifier.lowerHands": "Kéz letétele", + "app.statusNotifier.raisedHandsTitle": "Kézfeltartások", + "app.statusNotifier.raisedHandDesc": "{0} feltartotta a kezét", + "app.statusNotifier.and": "és", "app.switch.onLabel": "BE", "app.switch.offLabel": "KI", "app.talkingIndicator.ariaMuteDesc" : "Válasszd ki a némÃtani kÃvánt felhasználót", @@ -486,6 +507,8 @@ "app.userList.guest.pendingGuestUsers": "{0} várakozó vendégfelhasználó", "app.userList.guest.pendingGuestAlert": "csatlakozott a munkamenethez és várakozik a jóváhagyásodra.", "app.userList.guest.rememberChoice": "Választás megjegyzése", + "app.userList.guest.acceptLabel": "Engedélyezés", + "app.userList.guest.denyLabel": "Tiltás", "app.user-info.title": "CÃmtárkeresés", "app.toast.breakoutRoomEnded": "A csapatszoba bezárult. Csatlakozz újra a találkozóhoz.", "app.toast.chat.public": "Új nyilvános üzenet", @@ -500,6 +523,7 @@ "app.notification.recordingPaused": "Ezt a munkamenetet nem rögzÃtjük tovább", "app.notification.recordingAriaLabel": "Felvétel hossza", "app.notification.userJoinPushAlert": "{0} csatlakozott a munkamenethez", + "app.submenu.notification.raiseHandLabel": "Kézfeltartás", "app.shortcut-help.title": "Gyorsbillentyűk", "app.shortcut-help.accessKeyNotAvailable": "A hozzáférési kulcsok nem érhetÅ‘ek el", "app.shortcut-help.comboLabel": "Kombó", @@ -533,6 +557,12 @@ "app.lock-viewers.button.cancel": "Mégsem", "app.lock-viewers.locked": "Zárolt", "app.lock-viewers.unlocked": "Zárolás feloldva", + "app.connection-status.ariaTitle": "Kapcsolódási állapot modal", + "app.connection-status.title": "Kapcsolat állapota", + "app.connection-status.description": "Felhasználók kapcsolódási állapotának megjelenÃtése ", + "app.connection-status.empty": "Még nem történt jelentendÅ‘ csatlakozási probléma", + "app.connection-status.more": "több", + "app.connection-status.offline": "offline", "app.recording.startTitle": "Felvétel indÃtása", "app.recording.stopTitle": "Felvétel szüneteltetése", "app.recording.resumeTitle": "Felvétel folytatása", @@ -544,6 +574,9 @@ "app.videoPreview.closeLabel": "Bezárás", "app.videoPreview.findingWebcamsLabel": "Webkamerák keresése", "app.videoPreview.startSharingLabel": "Megosztás indÃtása", + "app.videoPreview.stopSharingLabel": "Megosztás leállÃtása", + "app.videoPreview.stopSharingAllLabel": "Összes megállÃtása", + "app.videoPreview.sharedCameraLabel": "Ez a webkamera még meg van osztva. ", "app.videoPreview.webcamOptionLabel": "Válassz webkamerát", "app.videoPreview.webcamPreviewLabel": "Webkamerám elÅ‘nézete", "app.videoPreview.webcamSettingsTitle": "Webkamerám beállÃtásai", @@ -573,19 +606,6 @@ "app.video.videoMenuDesc": "Videó lenyÃló menü megnyitása", "app.video.chromeExtensionError": "Szükséges telepÃtenie", "app.video.chromeExtensionErrorLink": "ez a Chrome bÅ‘vÃtmény", - "app.video.stats.title": "Kapcsolódási statisztikák", - "app.video.stats.packetsReceived": "Megérkezett csomagok", - "app.video.stats.packetsSent": "Elküldött csomagok", - "app.video.stats.packetsLost": "Elveszett csomagok", - "app.video.stats.bitrate": "Bitráta", - "app.video.stats.lostPercentage": "Összes veszteség százalékban", - "app.video.stats.lostRecentPercentage": "Jelenlegi veszteség százalékban", - "app.video.stats.dimensions": "Méretek", - "app.video.stats.codec": "Kodek", - "app.video.stats.decodeDelay": "Dekódolás késleltetése", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Kódolás használata", - "app.video.stats.currentDelay": "Jelenlegi elcsúszás", "app.fullscreenButton.label": "{0} teljes képernyÅ‘s módra állÃtása", "app.deskshare.iceConnectionStateError": "Kapcsolódási hiba a képernyÅ‘megosztáskor (ICE hiba 1108)", "app.sfu.mediaServerConnectionError2000": "Nem sikerült csatlakozni a médiaszerverhez (hiba 2000)", @@ -599,6 +619,7 @@ "app.sfu.noAvailableCodec2203": "A szerver nem találja a megfelelÅ‘ kodeket (hiba 2203)", "app.meeting.endNotification.ok.label": "OK", "app.whiteboard.annotations.poll": "A szavazás eredményeit sikeresen közzétetted", + "app.whiteboard.annotations.pollResult": "Szavazás eredménye", "app.whiteboard.toolbar.tools": "Eszközök", "app.whiteboard.toolbar.tools.hand": "Mozgatás", "app.whiteboard.toolbar.tools.pencil": "Ceruza", @@ -679,7 +700,7 @@ "app.externalVideo.autoPlayWarning": "Játszd le a videót a médiaszinkronizáció engedélyezéséhez", "app.network.connection.effective.slow": "Kapcsolódási problémát érzékeltünk.", "app.network.connection.effective.slow.help": "További információ", - "app.externalVideo.noteLabel": "Megjegyzés: A megosztott külsÅ‘ videó nem jelenik meg a felvételen. A YouTube, Vimeo, Instructure Media, Twitch és a Daily Motion URL-ek támogatottak.", + "app.externalVideo.noteLabel": "Megjegyzés: A megosztott külsÅ‘ videó nem jelenik meg a felvételen. A YouTube, Vimeo, Instructure Media, Twitch, Daily Motion és média URL-ek (például https://example.com/xy.mp4) támogatottak.", "app.actionsBar.actionsDropdown.shareExternalVideo": "Egy külsÅ‘ videó megosztása", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "KülsÅ‘ videó megosztásának befejezése", "app.iOSWarning.label": "FrissÃts iOS 12.2-re vagy újabbra", diff --git a/bigbluebutton-html5/private/locales/hy_AM.json b/bigbluebutton-html5/private/locales/hy.json similarity index 100% rename from bigbluebutton-html5/private/locales/hy_AM.json rename to bigbluebutton-html5/private/locales/hy.json diff --git a/bigbluebutton-html5/private/locales/id.json b/bigbluebutton-html5/private/locales/id.json index 0bf995e19c54911d2c340ea52ee34d4600b677af..3280cb4d99ea1a86a369647c98641b8ff4fbcf1f 100644 --- a/bigbluebutton-html5/private/locales/id.json +++ b/bigbluebutton-html5/private/locales/id.json @@ -7,7 +7,7 @@ "app.chat.inputLabel": "Masukan pesan untuk obrolan {0}", "app.chat.inputPlaceholder": "Kirim pesan ke {0}", "app.chat.titlePublic": "Obrolan Publik", - "app.chat.titlePrivate": "Obrolan pribadi dengan {0}", + "app.chat.titlePrivate": "Obrolan Pribadi dengan {0}", "app.chat.partnerDisconnected": "{0} sudah keluar dari pertemuan", "app.chat.closeChatLabel": "Tutup {0}", "app.chat.hideChatLabel": "Sembunyikan {0}", @@ -50,10 +50,10 @@ "app.note.title": "Catatan Bersama", "app.note.label": "Catatan", "app.note.hideNoteLabel": "Sembunyikan catatan", + "app.note.tipLabel": "Tekan Esc untuk fokus ke bilah alat penyunting", "app.user.activityCheck": "Pemeriksaan aktivitas pengguna", "app.user.activityCheck.label": "Periksa apakah pengguna masih dalam pertemuan ({0})", "app.user.activityCheck.check": "Periksa", - "app.note.tipLabel": "Tekan Esc untuk fokus ke bilah alat penyunting", "app.userList.usersTitle": "Pengguna", "app.userList.participantsTitle": "Peserta", "app.userList.messagesTitle": "Pesan", @@ -126,8 +126,10 @@ "app.meeting.meetingTimeRemaining": "Sisa waktu pertemuan: {0}", "app.meeting.meetingTimeHasEnded": "Waktu berakhir. Pertemuan akan segera ditutup", "app.meeting.endedMessage": "Anda akan diteruskan kembali ke layar beranda", - "app.meeting.alertMeetingEndsUnderOneMinute": "Pertemuan berakhir dalam satu menit.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Pecahan akan ditutup dalam satu menit.", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "Pertemuan berakhir dalam satu menit.", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "Pertemuan berakhir dalam {0} menit.", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "Pertemuan pecahan akan ditutup dalam {0} menit.", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "Pertemuan pecahan akan ditutup dalam satu menit.", "app.presentation.hide": "Sembunyikan presentasi", "app.presentation.notificationLabel": "Presentasi saat ini", "app.presentation.slideContent": "Isi Salindia", @@ -267,7 +269,7 @@ "app.leaveConfirmation.confirmLabel": "Keluar", "app.leaveConfirmation.confirmDesc": "Anda keluar dari meeting", "app.endMeeting.title": "Akhir pertemuan", - "app.endMeeting.description": "Anda yakin ingin mengakhiri sesi ini?", + "app.endMeeting.description": "Anda yakin ingin mengakhiri pertemuan ini untuk setiap orang (semua pengguna akan diputus)?", "app.endMeeting.yesLabel": "Ya", "app.endMeeting.noLabel": "Tidak", "app.about.title": "Tentang", @@ -288,10 +290,6 @@ "app.screenshare.screenShareLabel" : "Berbagi layar", "app.submenu.application.applicationSectionTitle": "Aplikasi", "app.submenu.application.animationsLabel": "Animasi", - "app.submenu.application.audioAlertLabel": "Peringatan Audio untuk Obrolan", - "app.submenu.application.pushAlertLabel": "Peringatan Popup untuk Obrolan", - "app.submenu.application.userJoinAudioAlertLabel": "Peringatan Audio untuk Pengguna Bergabung", - "app.submenu.application.userJoinPushAlertLabel": "Peringatan Popup untuk Pengguna Bergabung", "app.submenu.application.fontSizeControlLabel": "Ukuran fonta", "app.submenu.application.increaseFontBtnLabel": "Perbesar ukuran fonta aplikasi", "app.submenu.application.decreaseFontBtnLabel": "Perkecil ukuran fonta aplikasi", @@ -573,19 +571,6 @@ "app.video.videoMenuDesc": "Buka dropdown menu video", "app.video.chromeExtensionError": "Anda mesti memasang", "app.video.chromeExtensionErrorLink": "ekstensi Chrome ini", - "app.video.stats.title": "Statistik Koneksi", - "app.video.stats.packetsReceived": "Paket diterima", - "app.video.stats.packetsSent": "Paket dikirim", - "app.video.stats.packetsLost": "Paket hilang", - "app.video.stats.bitrate": "Laju bit", - "app.video.stats.lostPercentage": "Total persentase hilang", - "app.video.stats.lostRecentPercentage": "Baru-baru ini persentase hilang", - "app.video.stats.dimensions": "Dimensi", - "app.video.stats.codec": "Kodek", - "app.video.stats.decodeDelay": "Tundaan dekode", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Penggunaan enkode", - "app.video.stats.currentDelay": "Tundaan saat ini", "app.fullscreenButton.label": "Jadikan {0} layar penuh", "app.deskshare.iceConnectionStateError": "Koneksi gagal ketika berbagi layar (ICE galat 1108)", "app.sfu.mediaServerConnectionError2000": "Tidak bisa menyambung ke server media (galat 2000)", @@ -598,7 +583,6 @@ "app.sfu.invalidSdp2202":"Klien membuat permintaan media yang tidak valid (SDP galat 2202)", "app.sfu.noAvailableCodec2203": "Server tidak bisa menemukan kodek yang sesuai (galat 2203)", "app.meeting.endNotification.ok.label": "OK", - "app.whiteboard.annotations.poll": "Hasil poll dipublikasikan", "app.whiteboard.toolbar.tools": "Perkakas", "app.whiteboard.toolbar.tools.hand": "Pan", "app.whiteboard.toolbar.tools.pencil": "Pinsil", @@ -679,7 +663,6 @@ "app.externalVideo.autoPlayWarning": "Putar video untuk memfungsikan sinkronisasi media", "app.network.connection.effective.slow": "Kami mengamati masalah konektivitas.", "app.network.connection.effective.slow.help": "Informasi lebih jauh", - "app.externalVideo.noteLabel": "Catatan: Video eksternal yang dibagikan tidak akan muncul dalam rekaman. URL YouTube, Vimeo, Instructure Media, Twitch, dan Daily Motion didukung. ", "app.actionsBar.actionsDropdown.shareExternalVideo": "Berbagi suatu video eksternal", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Berhenti membagikan video eksternal", "app.iOSWarning.label": "Mohon tingkatkan ke iOS 12.2 atau lebih baru", diff --git a/bigbluebutton-html5/private/locales/it_IT.json b/bigbluebutton-html5/private/locales/it_IT.json index be8ce230a034171e69f9242b9a1eb04a5a6352be..bf4cbb902c8b66f0268d7507f70f27edac8c6d77 100644 --- a/bigbluebutton-html5/private/locales/it_IT.json +++ b/bigbluebutton-html5/private/locales/it_IT.json @@ -18,6 +18,7 @@ "app.chat.dropdown.save": "Salva", "app.chat.label": "Chat", "app.chat.offline": "Disconnesso", + "app.chat.pollResult": "Risultati Sondaggio", "app.chat.emptyLogLabel": "Il registro chat è vuoto", "app.chat.clearPublicChatMessage": "Il registro della Chat pubblica è stato cancellato da un moderatore", "app.chat.multi.typing": "Alcuni utenti stanno scrivendo", @@ -50,10 +51,10 @@ "app.note.title": "Note condivise", "app.note.label": "Note", "app.note.hideNoteLabel": "Nascondi nota", + "app.note.tipLabel": "Premi ESC per utilizzare la barra degli strumenti", "app.user.activityCheck": "Controllo attività utente", "app.user.activityCheck.label": "Controlla se l'utente è ancora nella meeting ({0})", "app.user.activityCheck.check": "Controlla", - "app.note.tipLabel": "Premi ESC per utilizzare la barra degli strumenti", "app.userList.usersTitle": "Utenti", "app.userList.participantsTitle": "Partecipanti", "app.userList.messagesTitle": "Messaggi", @@ -63,6 +64,7 @@ "app.userList.presenter": "Presentatore", "app.userList.you": "Tu", "app.userList.locked": "Bloccato", + "app.userList.byModerator": "dal (Moderatore)", "app.userList.label": "Lista utenti", "app.userList.toggleCompactView.label": "Attiva/Disattiva modalità compatta", "app.userList.guest": "Ospite", @@ -71,7 +73,9 @@ "app.userList.chatListItem.unreadPlural": "{0} Nuovi Messaggi", "app.userList.menu.chat.label": "Avvia una chat privata", "app.userList.menu.clearStatus.label": "Cancella stato", - "app.userList.menu.removeUser.label": "Rimuovi Utente", + "app.userList.menu.removeUser.label": "Rimuovi utente", + "app.userList.menu.removeConfirmation.label": "Rimuovi utente ({0})", + "app.userlist.menu.removeConfirmation.desc": "Impedire a questo utente di riconnettersi alla sessione.", "app.userList.menu.muteUserAudio.label": "Silenzia utente", "app.userList.menu.unmuteUserAudio.label": "Riattiva utente", "app.userList.userAriaLabel": "{0} {1} {2} Stato {3}", @@ -92,6 +96,7 @@ "app.userList.userOptions.unmuteAllDesc": "Riattiva l'audio del meeting", "app.userList.userOptions.lockViewersLabel": "Blocca spettatori", "app.userList.userOptions.lockViewersDesc": "Blocca alcune funzioni per i partecipanti al meeting", + "app.userList.userOptions.connectionStatusLabel": "Stato connessione", "app.userList.userOptions.disableCam": "La webcam degli spettatori è disabilitata", "app.userList.userOptions.disableMic": "Il microfono degli spettatori è disabilitato", "app.userList.userOptions.disablePrivChat": "Le chat private sono disabilitate", @@ -112,18 +117,21 @@ "app.media.screenshare.start": "Condivisione schermo avviata", "app.media.screenshare.end": "Condivisione schermo terminata", "app.media.screenshare.unavailable": "Condivisione schermo non disponibile", + "app.media.screenshare.notSupported": "La condivisione dello schermo non è supportata in questo browser.", "app.media.screenshare.autoplayBlockedDesc": "Abbiamo bisogno del tuo permesso per mostrarti lo schermo del presentatore", "app.media.screenshare.autoplayAllowLabel": "Visualizza schermo condiviso", "app.screenshare.notAllowed": "Errore: non è stato permesso l'accesso", - "app.screenshare.notSupportedError": "Errore: la condivisione schermo è disponibile solo sulle connessioni sicure (SSL)", + "app.screenshare.notSupportedError": "Errore: la condivisione dello schermo è disponibile solo sulle connessioni sicure (SSL)", "app.screenshare.notReadableError": "Errore: non è stato possibile acquisire il tuo schermo", - "app.screenshare.genericError": "Errore: si è verificato un problema con la condivisione schermo, riprova", + "app.screenshare.genericError": "Errore: si è verificato un problema con la condivisione dello schermo, riprova", "app.meeting.ended": "La sessione è terminata", "app.meeting.meetingTimeRemaining": "Tempo rimanente al termine del meeting: {0}", "app.meeting.meetingTimeHasEnded": "Tempo scaduto. Il meeting terminerà a breve", "app.meeting.endedMessage": "Verrai riportato alla pagina iniziale", - "app.meeting.alertMeetingEndsUnderOneMinute": "Il meeting terminerà entro un minuto.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Breakout terminerà entro un minuto", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "Il meeting termino tra un minuto.", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "Il meeting termino tra {0} minuti.", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "La stanza breakout termina tra {0} minuti.", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "La stanza breakout termina tra uno minuto.", "app.presentation.hide": "Nascondi presentazione", "app.presentation.notificationLabel": "Presentazione corrente", "app.presentation.slideContent": "Contenuto della slide", @@ -170,6 +178,7 @@ "app.presentationUploder.rejectedError": "Il file selezionato è stato rifiutato. controllare il tipo di file.", "app.presentationUploder.upload.progress": "Caricamento ({0}%)", "app.presentationUploder.upload.413": "Il file è troppo grande. Per favore dividilo in più file.", + "app.presentationUploder.genericError": "Oops, qualcosa è andato storto ...", "app.presentationUploder.upload.404": "404: Token di upload non valido", "app.presentationUploder.conversion.conversionProcessingSlides": "Elaborazione pagina {0} di {1}", "app.presentationUploder.conversion.genericConversionStatus": "Conversione file...", @@ -189,6 +198,9 @@ "app.presentationUploder.tableHeading.filename": "Nome del file", "app.presentationUploder.tableHeading.options": "Opzioni", "app.presentationUploder.tableHeading.status": "Stato", + "app.presentationUploder.uploadStatus": "{0} di {1} upload completati", + "app.presentationUploder.completed": "{0} upload completati", + "app.presentationUploder.clearErrors": "Pulisci gli errori", "app.poll.pollPaneTitle": "Domande/Sondaggi", "app.poll.quickPollTitle": "Sondaggio rapido", "app.poll.hidePollDesc": "Nascondi il menu sondaggi", @@ -196,7 +208,7 @@ "app.poll.quickPollInstruction": "Seleziona una delle seguenti opzioni per avviare il sondaggio", "app.poll.customPollLabel": "Sondaggio personalizzato", "app.poll.startCustomLabel": "Avvia una sondaggio personalizzato", - "app.poll.activePollInstruction": "Lascia questo pannello aperto per visualizzare le risposte in tempo reale al tuo sondaggio. Quando sei pronto, seleziona \"Pubblica risultati sondaggio\" per pubblicare i risultati alla fine del sondaggio.", + "app.poll.activePollInstruction": "Lascia questo pannello aperto per visualizzare le risposte in tempo reale al tuo sondaggio. Quando sei pronto, seleziona 'Pubblica risultati sondaggio' per pubblicare i risultati alla fine del sondaggio.", "app.poll.publishLabel": "Pubblica i risultati", "app.poll.backLabel": "Torna alle opzioni del sondaggio", "app.poll.closeLabel": "Chiudi", @@ -261,7 +273,7 @@ "app.leaveConfirmation.confirmLabel": "Abbandona", "app.leaveConfirmation.confirmDesc": "Abbandona il meeting", "app.endMeeting.title": "Fine Meeting", - "app.endMeeting.description": "Sei sicuro di voler terminare il meeting?", + "app.endMeeting.description": "Vuoi chiudere questo meeting per tutti (tutti gli utenti verranno disconnessi)?", "app.endMeeting.yesLabel": "Si", "app.endMeeting.noLabel": "No", "app.about.title": "Informazioni", @@ -282,10 +294,6 @@ "app.screenshare.screenShareLabel" : "Condivisione schermo", "app.submenu.application.applicationSectionTitle": "Applicazione", "app.submenu.application.animationsLabel": "Animazione", - "app.submenu.application.audioAlertLabel": "Notifiche audio per la Chat", - "app.submenu.application.pushAlertLabel": "Finestra di notifica Chat", - "app.submenu.application.userJoinAudioAlertLabel": "Notifica audio per ingresso utente", - "app.submenu.application.userJoinPushAlertLabel": "Notifica video per ingresso utente", "app.submenu.application.fontSizeControlLabel": "Dimensione carattere", "app.submenu.application.increaseFontBtnLabel": "Aumenta dimensione carattere", "app.submenu.application.decreaseFontBtnLabel": "Diminuisci dimensione carattere", @@ -293,6 +301,10 @@ "app.submenu.application.languageLabel": "Lingua dell'interfaccia", "app.submenu.application.languageOptionLabel": "Scegli linguaggio", "app.submenu.application.noLocaleOptionLabel": "Non ci sono localizzazioni attive", + "app.submenu.notification.SectionTitle": "Notifiche", + "app.submenu.notification.audioAlertLabel": "Notifiche Audio", + "app.submenu.notification.pushAlertLabel": "Notifiche in Finestra", + "app.submenu.notification.messagesLabel": "Messaggio in Chat", "app.submenu.audio.micSourceLabel": "Sorgente ingresso audio", "app.submenu.audio.speakerSourceLabel": "Sorgente uscita audio", "app.submenu.audio.streamVolumeLabel": "Volume del tuo streaming audio", @@ -316,6 +328,8 @@ "app.settings.dataSavingTab.screenShare": "Abilita Condivisione schermo", "app.settings.dataSavingTab.description": "Per risparmiare banda controlla quello che viene visualizzato", "app.settings.save-notification.label": "Impostazioni salvate", + "app.statusNotifier.lowerHands": "Abbassa la mano", + "app.statusNotifier.and": "e", "app.switch.onLabel": "ON", "app.switch.offLabel": "OFF", "app.talkingIndicator.ariaMuteDesc" : "Seleziona per disattivare l'audio utente", @@ -342,25 +356,25 @@ "app.actionsBar.actionsDropdown.takePresenterDesc": "Assegna al tuo utente il ruolo di presentatore", "app.actionsBar.emojiMenu.statusTriggerLabel": "Imposta Stato", "app.actionsBar.emojiMenu.awayLabel": "Non al computer", - "app.actionsBar.emojiMenu.awayDesc": "Imposta il tuo stato su \"non al computer\"", + "app.actionsBar.emojiMenu.awayDesc": "Imposta il tuo stato su 'non al computer'", "app.actionsBar.emojiMenu.raiseHandLabel": "Alza la mano", "app.actionsBar.emojiMenu.raiseHandDesc": "Alza la mano per porre una domanda", "app.actionsBar.emojiMenu.neutralLabel": "Indeciso", - "app.actionsBar.emojiMenu.neutralDesc": "Cambia il tuo stato a \"Indeciso\"", + "app.actionsBar.emojiMenu.neutralDesc": "Cambia il tuo stato a 'Indeciso'", "app.actionsBar.emojiMenu.confusedLabel": "Confuso", - "app.actionsBar.emojiMenu.confusedDesc": "Cambia il tuo stato in \"Confuso\"", + "app.actionsBar.emojiMenu.confusedDesc": "Cambia il tuo stato in 'Confuso'", "app.actionsBar.emojiMenu.sadLabel": "Triste", - "app.actionsBar.emojiMenu.sadDesc": "Cambia il tuo stato in \"Triste\"", + "app.actionsBar.emojiMenu.sadDesc": "Cambia il tuo stato in 'Triste'", "app.actionsBar.emojiMenu.happyLabel": "Felice", - "app.actionsBar.emojiMenu.happyDesc": "Cambia il tuo stato in \"Felice\"", + "app.actionsBar.emojiMenu.happyDesc": "Cambia il tuo stato in 'Felice'", "app.actionsBar.emojiMenu.noneLabel": "Cancella Stato", "app.actionsBar.emojiMenu.noneDesc": "Reimposta il tuo stato", "app.actionsBar.emojiMenu.applauseLabel": "Applausi", - "app.actionsBar.emojiMenu.applauseDesc": "Cambia il tuo stato in \"applausi\"", + "app.actionsBar.emojiMenu.applauseDesc": "Cambia il tuo stato in 'applausi'", "app.actionsBar.emojiMenu.thumbsUpLabel": "Pollice alzato", - "app.actionsBar.emojiMenu.thumbsUpDesc": "Cambia il tuo stato in \"pollice alzato\"", + "app.actionsBar.emojiMenu.thumbsUpDesc": "Cambia il tuo stato in 'pollice alzato'", "app.actionsBar.emojiMenu.thumbsDownLabel": "Contrariato", - "app.actionsBar.emojiMenu.thumbsDownDesc": "Cambia il tuo stato in \"contrariato\"", + "app.actionsBar.emojiMenu.thumbsDownDesc": "Cambia il tuo stato in 'contrariato'", "app.actionsBar.currentStatusDesc": "stato corrente {0}", "app.actionsBar.captions.start": "Avvia riproduzione dei sottotitoli", "app.actionsBar.captions.stop": "Interrompi riproduzione sottotitoli", @@ -480,6 +494,8 @@ "app.userList.guest.pendingGuestUsers": "{0} ospiti in attesa", "app.userList.guest.pendingGuestAlert": "ha effettuato l'accesso alla sessione ed è in attesa di approvazione.", "app.userList.guest.rememberChoice": "Ricorda la scelta", + "app.userList.guest.acceptLabel": "Accetta", + "app.userList.guest.denyLabel": "Nega", "app.user-info.title": "Cerca directory", "app.toast.breakoutRoomEnded": "La Stanza Separata è terminata. Torna al meeting.", "app.toast.chat.public": "Nuovo messaggio nella chat pubblica", @@ -494,6 +510,7 @@ "app.notification.recordingPaused": "La registrazione della sessione è stata interrotta", "app.notification.recordingAriaLabel": "Durata registrazione", "app.notification.userJoinPushAlert": "{0} sta partecipando alla sessione", + "app.submenu.notification.raiseHandLabel": "Alza la mano", "app.shortcut-help.title": "Scorciatoie da tastiera", "app.shortcut-help.accessKeyNotAvailable": "Non sono disponibili chiavi di accesso", "app.shortcut-help.comboLabel": "Combinazione", @@ -526,6 +543,8 @@ "app.lock-viewers.button.cancel": "Annulla", "app.lock-viewers.locked": "Bloccato", "app.lock-viewers.unlocked": "Sbloccato", + "app.connection-status.title": "Stato connessione", + "app.connection-status.offline": "Disconnesso", "app.recording.startTitle": "Avvia registrazione", "app.recording.stopTitle": "Sospendi registrazione", "app.recording.resumeTitle": "Riprendi registrazione", @@ -537,6 +556,8 @@ "app.videoPreview.closeLabel": "Chiudi", "app.videoPreview.findingWebcamsLabel": "Ricerca webcams", "app.videoPreview.startSharingLabel": "Avvia condivisione", + "app.videoPreview.stopSharingLabel": "Termina condivisione", + "app.videoPreview.stopSharingAllLabel": "Termina tutto", "app.videoPreview.webcamOptionLabel": "Scegli webcam", "app.videoPreview.webcamPreviewLabel": "Anteprima webcam", "app.videoPreview.webcamSettingsTitle": "Impostazioni webcam", @@ -566,19 +587,6 @@ "app.video.videoMenuDesc": "Apre il menu video", "app.video.chromeExtensionError": "Devi installare", "app.video.chromeExtensionErrorLink": "questa estensione per Chrome", - "app.video.stats.title": "Statistiche connessione", - "app.video.stats.packetsReceived": "Pacchetti ricevuti", - "app.video.stats.packetsSent": "Pacchetti inviati", - "app.video.stats.packetsLost": "Pacchetti persi", - "app.video.stats.bitrate": "Bitrate", - "app.video.stats.lostPercentage": "Percentuale totale persi", - "app.video.stats.lostRecentPercentage": "Percentuale recenti persi", - "app.video.stats.dimensions": "Dimensioni", - "app.video.stats.codec": "Codec", - "app.video.stats.decodeDelay": "Ritardo di decodifica", - "app.video.stats.rtt": "Real Time", - "app.video.stats.encodeUsagePercent": "Utilizzo decodifica", - "app.video.stats.currentDelay": "Ritardo attuale", "app.fullscreenButton.label": "Metti {0} a schermo intero", "app.deskshare.iceConnectionStateError": "Connessione fallita nella condivisione dello schermo (errore ICE 1108)", "app.sfu.mediaServerConnectionError2000": "Impossibile connettersi al server dei media (errore 2000)", @@ -591,7 +599,6 @@ "app.sfu.invalidSdp2202":"Il client ha generato una richiesta di media invalida (errore SDP 2202)", "app.sfu.noAvailableCodec2203": "Il server non ha trovato un codec appropriato (error 2203)", "app.meeting.endNotification.ok.label": "OK", - "app.whiteboard.annotations.poll": "I risultati del sondaggio sono stati pubblicati", "app.whiteboard.toolbar.tools": "Strumenti", "app.whiteboard.toolbar.tools.hand": "Panoramica", "app.whiteboard.toolbar.tools.pencil": "Matita", @@ -672,7 +679,7 @@ "app.externalVideo.autoPlayWarning": "Avvia il video per abilitare la sincronizzazione media", "app.network.connection.effective.slow": "Stiamo rilevando problemi di connessione", "app.network.connection.effective.slow.help": "Più informazioni", - "app.externalVideo.noteLabel": "Nota: I video esterni condivisi non appariranno nella registrazione. Sono supportati i link di YouTube, Vimeo, Instructure Media, Twitch e Daily Motion.", + "app.externalVideo.noteLabel": "Nota: I video esterni non appariranno nella registrazione. Sono supportati YouTube, Vimeo, Instructure Media, Twitch, Dailymotion and URL di file multimediali (es. https://example.com/xy.mp4).", "app.actionsBar.actionsDropdown.shareExternalVideo": "Condividi un video esterno", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Interrompi la condivisione di un video esterno", "app.iOSWarning.label": "Aggiornare alla versione iOS 12.2 o seguenti", diff --git a/bigbluebutton-html5/private/locales/ja.json b/bigbluebutton-html5/private/locales/ja.json index 441f1c68c8844a4e84b93501ed2f74be7c57016b..c1d6e07af35dcedcc740ca44c7810056fd968a3d 100644 --- a/bigbluebutton-html5/private/locales/ja.json +++ b/bigbluebutton-html5/private/locales/ja.json @@ -18,6 +18,7 @@ "app.chat.dropdown.save": "セーブ", "app.chat.label": "ãƒãƒ£ãƒƒãƒˆ", "app.chat.offline": "オフライン", + "app.chat.pollResult": "æŠ•ç¥¨çµæžœ", "app.chat.emptyLogLabel": "ãƒãƒ£ãƒƒãƒˆãƒã‚°ã¯ç©ºã§ã™", "app.chat.clearPublicChatMessage": "ãƒãƒ£ãƒƒãƒˆå±¥æ´ã¯ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚¿ãƒ¼ã«ã‚ˆã‚Šæ¶ˆåŽ»ã•れã¾ã—ãŸ", "app.chat.multi.typing": "複数ã®äººãŒå…¥åŠ›ä¸", @@ -50,10 +51,10 @@ "app.note.title": "共有メモ", "app.note.label": "メモ", "app.note.hideNoteLabel": "ãƒ¡ãƒ¢ã‚’éš ã™", + "app.note.tipLabel": "Escを押ã—ã¦ã‚¨ãƒ‡ã‚£ã‚¿ãƒ¼ãƒ„ールãƒãƒ¼ã«ãƒ•ォーカスã™ã‚‹", "app.user.activityCheck": "ユーザーアクティビティ確èª", "app.user.activityCheck.label": "会è°({0})ã«ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒã¾ã å‚åŠ ã—ã¦ã„ã‚‹ã‹ç¢ºèªã™ã‚‹", "app.user.activityCheck.check": "確èª", - "app.note.tipLabel": "Escを押ã—ã¦ã‚¨ãƒ‡ã‚£ã‚¿ãƒ¼ãƒ„ールãƒãƒ¼ã«ãƒ•ォーカスã™ã‚‹", "app.userList.usersTitle": "ユーザー", "app.userList.participantsTitle": "å‚åŠ è€…", "app.userList.messagesTitle": "メッセージ", @@ -76,7 +77,7 @@ "app.userList.menu.removeConfirmation.label": "ユーザー({0})ã®é€€å®¤", "app.userlist.menu.removeConfirmation.desc": "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’二度ã¨ã‚»ãƒƒã‚·ãƒ§ãƒ³ã«å‚åŠ ã•ã›ãªã„", "app.userList.menu.muteUserAudio.label": "ユーザーをミュートã™ã‚‹", - "app.userList.menu.unmuteUserAudio.label": "ユーザーã®ãƒŸãƒ¥ãƒ¼ãƒˆã‚’外ã™", + "app.userList.menu.unmuteUserAudio.label": "ユーザーã®ãƒŸãƒ¥ãƒ¼ãƒˆã‚’解除ã™ã‚‹", "app.userList.userAriaLabel": "{0} {1} {2} ステータス {3}", "app.userList.menu.promoteUser.label": "モデレーターã«ã™ã‚‹", "app.userList.menu.demoteUser.label": "ãƒ“ãƒ¥ãƒ¼ã‚¢ãƒ¼ã«æˆ»ã™", @@ -92,9 +93,11 @@ "app.userList.userOptions.muteAllExceptPresenterLabel": "プレゼンター以外ã®å…¨ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’ミュートã«ã™ã‚‹", "app.userList.userOptions.muteAllExceptPresenterDesc": "ã“ã®ä¼šè°ã®ãƒ—レゼンター以外ã®å…¨ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’ミュートã«ã™ã‚‹", "app.userList.userOptions.unmuteAllLabel": "会è°ã®ãƒŸãƒ¥ãƒ¼ãƒˆã‚’オフã«ã™ã‚‹", - "app.userList.userOptions.unmuteAllDesc": "会è°ã®ãƒŸãƒ¥ãƒ¼ãƒˆã‚’外ã™", + "app.userList.userOptions.unmuteAllDesc": "会è°ã®ãƒŸãƒ¥ãƒ¼ãƒˆã‚’解除ã™ã‚‹", "app.userList.userOptions.lockViewersLabel": "ビューアーをãƒãƒƒã‚¯ã™ã‚‹", "app.userList.userOptions.lockViewersDesc": "ã“ã®ä¼šè°ã®å‚åŠ è€…ã®ãŸã‚ã®ç‰¹å®šã®æ©Ÿèƒ½ã‚’ãƒãƒƒã‚¯ã™ã‚‹", + "app.userList.userOptions.connectionStatusLabel": "接続状æ³", + "app.userList.userOptions.connectionStatusDesc": "ãƒ¦ãƒ¼ã‚¶ã®æŽ¥ç¶šçŠ¶æ³ã‚’ã¿ã‚‹", "app.userList.userOptions.disableCam": "ビューアーã®ã‚¦ã‚§ãƒ–カメラã¯åˆ©ç”¨ã§ãã¾ã›ã‚“", "app.userList.userOptions.disableMic": "ビューアーã®ãƒžã‚¤ã‚¯ã¯åˆ©ç”¨ã§ãã¾ã›ã‚“", "app.userList.userOptions.disablePrivChat": "éžå…¬é–‹ãƒãƒ£ãƒƒãƒˆã¯åˆ©ç”¨ã§ãã¾ã›ã‚“", @@ -126,10 +129,13 @@ "app.meeting.meetingTimeRemaining": "会è°ã®æ®‹ã‚Šæ™‚間:{0}", "app.meeting.meetingTimeHasEnded": "時間終了。会è°ã¯ã¾ã‚‚ãªã終了ã—ã¾ã™ã€‚", "app.meeting.endedMessage": "ãƒ›ãƒ¼ãƒ ã‚¹ã‚¯ãƒªãƒ¼ãƒ³ã«æˆ»ã‚Šã¾ã™", - "app.meeting.alertMeetingEndsUnderOneMinute": "ã‚ã¨1分ã§ä¼šè°ã¯çµ‚了ã—ã¾ã™ã€‚", - "app.meeting.alertBreakoutEndsUnderOneMinute": "ã‚ã¨1分ã§ãƒ–レイクアウトセッションã¯çµ‚了ã—ã¾ã™ã€‚", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "会è°ã¯ã‚ã¨ä¸€åˆ†ã§çµ‚了ã—ã¾ã™ã€‚", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "会è°ã¯ã‚ã¨{0}分ã§çµ‚了ã—ã¾ã™ã€‚", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "ã“ã®å°ä¼šè°ã¯ã‚ã¨{0}分ã§çµ‚了ã—ã¾ã™ã€‚", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "ã“ã®å°ä¼šè°ã¯ã‚ã¨ä¸€åˆ†ã§çµ‚了ã—ã¾ã™ã€‚", "app.presentation.hide": "プレゼンテーションをéžè¡¨ç¤º", "app.presentation.notificationLabel": "ç¾åœ¨ã®ãƒ—レゼンテーション", + "app.presentation.downloadLabel": "ダウンãƒãƒ¼ãƒ‰", "app.presentation.slideContent": "スライドコンテンツ", "app.presentation.startSlideContent": "スライドコンテンツ開始", "app.presentation.endSlideContent": "スライドコンテンツ終了", @@ -174,6 +180,7 @@ "app.presentationUploder.rejectedError": "é¸æŠžãƒ•ã‚¡ã‚¤ãƒ«ãŒæ‹’å¦ã•れã¾ã—ãŸã€‚ファイル形å¼ã‚’確èªã—ã¦ãã ã•ã„。", "app.presentationUploder.upload.progress": "アップãƒãƒ¼ãƒ‰ä¸({0}%)", "app.presentationUploder.upload.413": "ファイルãŒå¤§ãã™ãŽã¾ã™ã€‚ã„ãã¤ã‹ã®ãƒ•ァイルã«åˆ†å‰²ã—ã¦ãã ã•ã„。", + "app.presentationUploder.genericError": "申ã—訳ã‚りã¾ã›ã‚“ãŒã€ä½•ã‹å•題ãŒã‚るよã†ã§ã™ã€‚", "app.presentationUploder.upload.408": "アップãƒãƒ¼ãƒ‰ãƒˆãƒ¼ã‚¯ãƒ³ã®è¦æ±‚ãŒæ™‚間切れã«ãªã‚Šã¾ã—ãŸã€‚", "app.presentationUploder.upload.404": "エラー404:無効ãªã‚¢ãƒƒãƒ—ãƒãƒ¼ãƒ‰ãƒˆãƒ¼ã‚¯ãƒ³", "app.presentationUploder.upload.401": "アップãƒãƒ¼ãƒ‰ãƒˆãƒ¼ã‚¯ãƒ³ã®è¦æ±‚ãŒå¤±æ•—ã—ã¾ã—ãŸã€‚", @@ -195,6 +202,13 @@ "app.presentationUploder.tableHeading.filename": "ファイルå", "app.presentationUploder.tableHeading.options": "オプション", "app.presentationUploder.tableHeading.status": "ステータス", + "app.presentationUploder.uploading": "{0} {1} アップãƒãƒ¼ãƒ‰ä¸", + "app.presentationUploder.uploadStatus": "{1}個ã®ã†ã¡{0}個をアップãƒãƒ¼ãƒ‰ã—ã¾ã—ãŸ", + "app.presentationUploder.completed": "{0}個をアップãƒãƒ¼ãƒ‰ã—ã¾ã—ãŸ", + "app.presentationUploder.item" : "資料", + "app.presentationUploder.itemPlural" : "資料", + "app.presentationUploder.clearErrors": "エラー消去", + "app.presentationUploder.clearErrorsDesc": "アップãƒãƒ¼ãƒ‰ã«å¤±æ•—ã—ãŸãƒ—レゼン資料を消去ã—ã¾ã™", "app.poll.pollPaneTitle": "投票", "app.poll.quickPollTitle": "簡易投票", "app.poll.hidePollDesc": "投票メニュー画é¢ã‚’éš ã™", @@ -240,6 +254,7 @@ "app.connectingMessage": "接続ä¸...", "app.waitingMessage": "接続ãŒåˆ‡ã‚Œã¾ã—ãŸã€‚ {0} ç§’ã§å†æŽ¥ç¶šã—ã¾ã™ ...", "app.retryNow": "リトライ", + "app.muteWarning.label": "自分ã®ãƒŸãƒ¥ãƒ¼ãƒˆã‚’解除ã™ã‚‹ãŸã‚{0}をクリック", "app.navBar.settingsDropdown.optionsLabel": "オプション", "app.navBar.settingsDropdown.fullscreenLabel": "全画é¢è¡¨ç¤ºã«åˆ‡æ›¿", "app.navBar.settingsDropdown.settingsLabel": "è¨å®šã‚’é–‹ã", @@ -267,7 +282,7 @@ "app.leaveConfirmation.confirmLabel": "退室", "app.leaveConfirmation.confirmDesc": "自分を会è°ã‹ã‚‰ãƒã‚°ã‚¢ã‚¦ãƒˆã•ã›ã‚‹", "app.endMeeting.title": "会è°ã‚’終了ã™ã‚‹", - "app.endMeeting.description": "ã“ã®ä¼šè°ã‚’終了ã—ã¾ã™ã‹ï¼Ÿ", + "app.endMeeting.description": "本当ã«ã“ã®ä¼šè°ã‚’終了(ã™ã¹ã¦ã®å‚åŠ è€…ã®æŽ¥ç¶šã‚’åˆ‡æ–)ã—ã¦ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ", "app.endMeeting.yesLabel": "ã¯ã„", "app.endMeeting.noLabel": "ã„ã„ãˆ", "app.about.title": "è£½å“æƒ…å ±", @@ -288,10 +303,6 @@ "app.screenshare.screenShareLabel" : "ç”»é¢å…±æœ‰", "app.submenu.application.applicationSectionTitle": "アプリケーション", "app.submenu.application.animationsLabel": "アニメーション", - "app.submenu.application.audioAlertLabel": "ãƒãƒ£ãƒƒãƒˆã®éŸ³å£°é€šçŸ¥", - "app.submenu.application.pushAlertLabel": "ãƒãƒ£ãƒƒãƒˆã®ãƒãƒƒãƒ—アップ通知", - "app.submenu.application.userJoinAudioAlertLabel": "ユーザå‚åŠ ã®éŸ³å£°é€šçŸ¥", - "app.submenu.application.userJoinPushAlertLabel": "ユーザå‚åŠ ã®ãƒãƒƒãƒ—アップ", "app.submenu.application.fontSizeControlLabel": "フォントサイズ", "app.submenu.application.increaseFontBtnLabel": "アプリケーションã®ãƒ•ォントサイズを大ããã™ã‚‹", "app.submenu.application.decreaseFontBtnLabel": "アプリケーションã®ãƒ•ォントサイズをå°ã•ãã™ã‚‹", @@ -299,6 +310,12 @@ "app.submenu.application.languageLabel": "アプリケーション言語", "app.submenu.application.languageOptionLabel": "è¨€èªžã‚’é¸æŠž", "app.submenu.application.noLocaleOptionLabel": "アクティブãªãƒã‚±ãƒ¼ãƒ«ãŒã‚りã¾ã›ã‚“", + "app.submenu.notification.SectionTitle": "通知", + "app.submenu.notification.Desc": "何をã©ã®ã‚ˆã†ã«é€šçŸ¥ã™ã‚‹ã‹ã‚’è¨å®šã—ã¦ãã ã•ã„。", + "app.submenu.notification.audioAlertLabel": "音声通知", + "app.submenu.notification.pushAlertLabel": "ãƒãƒƒãƒ—アップ通知", + "app.submenu.notification.messagesLabel": "ãƒãƒ£ãƒƒãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸", + "app.submenu.notification.userJoinLabel": "ユーザã®å‚åŠ ", "app.submenu.audio.micSourceLabel": "マイクã®ã‚½ãƒ¼ã‚¹", "app.submenu.audio.speakerSourceLabel": "スピーカーã®ã‚½ãƒ¼ã‚¹", "app.submenu.audio.streamVolumeLabel": "音声ストリームã®éŸ³é‡", @@ -322,6 +339,10 @@ "app.settings.dataSavingTab.screenShare": "デスクトップ共有を有効ã«ã™ã‚‹", "app.settings.dataSavingTab.description": "通信é‡ã‚’減らã™ãŸã‚è¨å®šã‚’変更ã—ã¦ãã ã•ã„", "app.settings.save-notification.label": "è¨å®šãŒä¿å˜ã•れã¾ã—ãŸ", + "app.statusNotifier.lowerHands": "手をãŠã‚ã™", + "app.statusNotifier.raisedHandsTitle": "手を上ã’ã‚‹", + "app.statusNotifier.raisedHandDesc": "{0}äººãŒæ‰‹ã‚’上ã’ã¾ã—ãŸ", + "app.statusNotifier.and": "ã¨", "app.switch.onLabel": "å…¥", "app.switch.offLabel": "切", "app.talkingIndicator.ariaMuteDesc" : "ユーザをミュートã—ã¾ã™", @@ -484,8 +505,10 @@ "app.userList.guest.denyEveryone": "全員を拒å¦ã™ã‚‹", "app.userList.guest.pendingUsers": "{0} ä¿ç•™ä¸ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼", "app.userList.guest.pendingGuestUsers": "{0} ä¿ç•™ä¸ã®ã‚²ã‚¹ãƒˆãƒ¦ãƒ¼ã‚¶ãƒ¼", - "app.userList.guest.pendingGuestAlert": "セッションã«å‚åŠ ã—ã€è¨±å¯ã‚’å¾…ã£ã¦ã„ã¾ã™ã€‚", + "app.userList.guest.pendingGuestAlert": "ã¯ã‚»ãƒƒã‚·ãƒ§ãƒ³ã«å‚åŠ ã—ã€è¨±å¯ã‚’å¾…ã£ã¦ã„ã¾ã™ã€‚", "app.userList.guest.rememberChoice": "é¸æŠžã‚’è¨˜æ†¶ã•ã›ã‚‹", + "app.userList.guest.acceptLabel": "承èª", + "app.userList.guest.denyLabel": "å´ä¸‹", "app.user-info.title": "ディレクトリ検索", "app.toast.breakoutRoomEnded": "å°ä¼šè°ãŒçµ‚了ã—ã¾ã—ãŸã€‚音声ã§ã‚‚ã†ä¸€åº¦å‚åŠ ã—ã¦ãã ã•ã„。", "app.toast.chat.public": "æ–°ã—ã„グループãƒãƒ£ãƒƒãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸", @@ -497,9 +520,10 @@ "app.toast.meetingMuteOff.label": "会è°ã®ãƒŸãƒ¥ãƒ¼ãƒˆã‚’解除ã—ã¾ã—ãŸ", "app.notification.recordingStart": "録画ãŒé–‹å§‹ã—ã¾ã—ãŸ", "app.notification.recordingStop": "ã“ã®ã‚»ãƒƒã‚·ãƒ§ãƒ³ã¯éŒ²ç”»ã•れã¦ã„ã¾ã›ã‚“", - "app.notification.recordingPaused": "録画ãŒçµ‚了ã—ã¾ã—ãŸ", + "app.notification.recordingPaused": "ã“ã®ã‚»ãƒƒã‚·ãƒ§ãƒ³ã¯ã‚‚ã†éŒ²ç”»ã•れã¦ã„ã¾ã›ã‚“", "app.notification.recordingAriaLabel": "録画時間", "app.notification.userJoinPushAlert": "{0} ãŒã‚»ãƒƒã‚·ãƒ§ãƒ³ã«å‚åŠ ã—ã¾ã—ãŸ", + "app.submenu.notification.raiseHandLabel": "挙手", "app.shortcut-help.title": "ã‚ーボードショートカット", "app.shortcut-help.accessKeyNotAvailable": "使用ã§ããªã„アクセスã‚ー", "app.shortcut-help.comboLabel": "コンボ", @@ -533,6 +557,12 @@ "app.lock-viewers.button.cancel": "ã‚ャンセル", "app.lock-viewers.locked": "ãƒãƒƒã‚¯", "app.lock-viewers.unlocked": "ãƒãƒƒã‚¯è§£é™¤", + "app.connection-status.ariaTitle": "接続状æ³ãƒ¢ãƒ¼ãƒ€ãƒ«", + "app.connection-status.title": "接続状æ³", + "app.connection-status.description": "ãƒ¦ãƒ¼ã‚¶ã®æŽ¥ç¶šçŠ¶æ³ã‚’ã¿ã‚‹", + "app.connection-status.empty": "ç¾åœ¨ã®ã¨ã“ã‚æŽ¥ç¶šã«é–¢ã™ã‚‹å•題ã¯ã¿ã¨ã‚られã¾ã›ã‚“", + "app.connection-status.more": "æ›´ã«è¦‹ã‚‹", + "app.connection-status.offline": "オフライン", "app.recording.startTitle": "録画開始", "app.recording.stopTitle": "éŒ²ç”»ä¸€æ™‚åœæ¢", "app.recording.resumeTitle": "録画å†é–‹", @@ -540,10 +570,17 @@ "app.recording.stopDescription": "録画をæ¢ã‚ã¾ã™ã‹ï¼Ÿã‚‚ã†ä¸€åº¦ãƒœã‚¿ãƒ³ã‚’クリックã™ã‚‹ã¨ã¾ãŸå†é–‹ã—ã¾ã™", "app.videoPreview.cameraLabel": "カメラ", "app.videoPreview.profileLabel": "å“質", + "app.videoPreview.quality.low": "低", + "app.videoPreview.quality.medium": "ä¸", + "app.videoPreview.quality.high": "高", + "app.videoPreview.quality.hd": "HD", "app.videoPreview.cancelLabel": "ã‚ャンセル", "app.videoPreview.closeLabel": "é–‰ã˜ã‚‹", "app.videoPreview.findingWebcamsLabel": "ウェブカメラå–å¾—ä¸", "app.videoPreview.startSharingLabel": "共有を開始", + "app.videoPreview.stopSharingLabel": "å…±æœ‰ã‚’åœæ¢", + "app.videoPreview.stopSharingAllLabel": "å…¨ã¦ã®å…±æœ‰ã‚’åœæ¢", + "app.videoPreview.sharedCameraLabel": "ã‚«ãƒ¡ãƒ©ã¯æ—¢ã«å…±æœ‰ã•れã¦ã„ã¾ã™", "app.videoPreview.webcamOptionLabel": "ã‚¦ã‚§ãƒ–ã‚«ãƒ¡ãƒ©ã‚’é¸æŠž", "app.videoPreview.webcamPreviewLabel": "ウェブカメラã®ãƒ—レビュー", "app.videoPreview.webcamSettingsTitle": "ウェブカメラè¨å®š", @@ -573,19 +610,8 @@ "app.video.videoMenuDesc": "ビデオメニューã®ãƒ‰ãƒãƒƒãƒ—ダウンを開ã", "app.video.chromeExtensionError": "インストールã—ã¦ãã ã•ã„", "app.video.chromeExtensionErrorLink": "ã“ã®Chromeã®æ‹¡å¼µæ©Ÿèƒ½", - "app.video.stats.title": "æŽ¥ç¶šæƒ…å ±", - "app.video.stats.packetsReceived": "パケットå—ä¿¡", - "app.video.stats.packetsSent": "パケットé€ä¿¡", - "app.video.stats.packetsLost": "エラーパケット", - "app.video.stats.bitrate": "ビットレート", - "app.video.stats.lostPercentage": "ç·æå¤±çŽ‡", - "app.video.stats.lostRecentPercentage": "æœ€æ–°ã®æå¤±çŽ‡", - "app.video.stats.dimensions": "è§£åƒåº¦", - "app.video.stats.codec": "コーデック", - "app.video.stats.decodeDelay": "é…延(ディコード)", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "エンコードã®ä½¿ç”¨çжæ³", - "app.video.stats.currentDelay": "é…延(最新)", + "app.video.pagination.prevPage": "å‰ã®ãƒ“デオを見る", + "app.video.pagination.nextPage": "次ã®ãƒ“デオを見る", "app.fullscreenButton.label": "{0}を全画é¢ã«åˆ‡ã‚Šæ›¿ãˆã‚‹", "app.deskshare.iceConnectionStateError": "ç”»é¢å…±æœ‰ã®æŽ¥ç¶šã«å¤±æ•—ã—ã¾ã—㟠(ICE error 1108)", "app.sfu.mediaServerConnectionError2000": "メディアサーãƒãƒ¼ã«æŽ¥ç¶šã§ãã¾ã›ã‚“ (error 2000)", @@ -599,6 +625,7 @@ "app.sfu.noAvailableCodec2203": "é©åˆ‡ãªã‚³ãƒ¼ãƒ‡ãƒƒã‚¯ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—㟠(error 2203)", "app.meeting.endNotification.ok.label": "OK", "app.whiteboard.annotations.poll": "æŠ•ç¥¨çµæžœãŒå…¬é–‹ã•れã¾ã—ãŸ", + "app.whiteboard.annotations.pollResult": "æŠ•ç¥¨çµæžœ", "app.whiteboard.toolbar.tools": "ツール", "app.whiteboard.toolbar.tools.hand": "パン", "app.whiteboard.toolbar.tools.pencil": "ペン", @@ -656,7 +683,7 @@ "app.createBreakoutRoom.confirm": "作æˆ", "app.createBreakoutRoom.record": "録画", "app.createBreakoutRoom.numberOfRooms": "会è°å®¤æ•°", - "app.createBreakoutRoom.durationInMinutes": "利用時間(秒)", + "app.createBreakoutRoom.durationInMinutes": "利用時間(分)", "app.createBreakoutRoom.randomlyAssign": "ランダムã«å‰²ã‚Šå½“ã¦ã‚‹", "app.createBreakoutRoom.endAllBreakouts": "å…¨ã¦ã®å°ä¼šè°å®¤ã‚’終了ã™ã‚‹", "app.createBreakoutRoom.roomName": "{0}(会è°å®¤ï¼{1})", @@ -679,7 +706,7 @@ "app.externalVideo.autoPlayWarning": "éŸ³å£°åŒæœŸã™ã‚‹ã«ã¯å‹•画をå†ç”Ÿã—ã¦ãã ã•ã„", "app.network.connection.effective.slow": "接続ã®å•題ãŒç™ºç”Ÿã—ã¾ã—ãŸ", "app.network.connection.effective.slow.help": "ヘルプ", - "app.externalVideo.noteLabel": "注æ„:インターãƒãƒƒãƒˆä¸Šã®å‹•ç”»ã¯éŒ²ç”»ã§ãã¾ã›ã‚“。YouTube, Vimeo, Instructure Media, Twitch, Daily Motionã®URLãŒã‚µãƒãƒ¼ãƒˆã•れã¦ã„ã¾ã™", + "app.externalVideo.noteLabel": "注æ„:インターãƒãƒƒãƒˆä¸Šã®å‹•ç”»ã¯éŒ²ç”»ã§ãã¾ã›ã‚“。YouTube, Vimeo, Instructure Media, Twitch, Daily Motion, ãŠã‚ˆã³ãƒ¡ãƒ‡ã‚£ã‚¢ãƒ•ァイルã®URL(例ãˆã°https://example.com/xy.mp4)ãŒã‚µãƒãƒ¼ãƒˆã•れã¦ã„ã¾ã™", "app.actionsBar.actionsDropdown.shareExternalVideo": "動画を共有ã™ã‚‹", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "å‹•ç”»ã®å…±æœ‰åœæ¢", "app.iOSWarning.label": "iOS 12.2ã¾ãŸã¯ãれ以é™ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«ã‚¢ãƒƒãƒ—グレードã—ã¦ãã ã•ã„", diff --git a/bigbluebutton-html5/private/locales/ja_JP.json b/bigbluebutton-html5/private/locales/ja_JP.json index 98faea372fc8a574e6a6e4eeafe557584494c283..c64512f5062d558a7f22e6aba1cd7b3b976e45ee 100644 --- a/bigbluebutton-html5/private/locales/ja_JP.json +++ b/bigbluebutton-html5/private/locales/ja_JP.json @@ -18,6 +18,7 @@ "app.chat.dropdown.save": "ä¿å˜", "app.chat.label": "ãƒãƒ£ãƒƒãƒˆ", "app.chat.offline": "オフライン", + "app.chat.pollResult": "æŠ•ç¥¨çµæžœ", "app.chat.emptyLogLabel": "ãƒãƒ£ãƒƒãƒˆãƒã‚°ã¯ç©ºã§ã™", "app.chat.clearPublicChatMessage": "グループãƒãƒ£ãƒƒãƒˆå±¥æ´ã¯ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚¿ãƒ¼ã«ã‚ˆã‚Šæ¶ˆåŽ»ã•れã¾ã—ãŸ", "app.chat.multi.typing": "複数ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒå…¥åŠ›ã—ã¦ã„ã¾ã™", @@ -50,10 +51,10 @@ "app.note.title": "共有メモ", "app.note.label": "メモ", "app.note.hideNoteLabel": "ãƒ¡ãƒ¢ã‚’éš ã™", + "app.note.tipLabel": "ツールãƒãƒ¼ã‹ã‚‰å½¢å¼ã‚’é¸æŠžã™ã‚‹ã«ã¯Escã‚ーを押ã—ã¾ã™", "app.user.activityCheck": "ユーザーアクティビティ確èª", "app.user.activityCheck.label": "会è°({0})ã«å‚åŠ ä¸ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒã„ã‚‹ã‹ç¢ºèªã™ã‚‹", "app.user.activityCheck.check": "確èª", - "app.note.tipLabel": "ツールãƒãƒ¼ã‹ã‚‰å½¢å¼ã‚’é¸æŠžã™ã‚‹ã«ã¯Escã‚ーを押ã—ã¾ã™", "app.userList.usersTitle": "ユーザー", "app.userList.participantsTitle": "å‚åŠ è€…", "app.userList.messagesTitle": "メッセージ", @@ -76,7 +77,7 @@ "app.userList.menu.removeConfirmation.label": "ユーザー({0})ã®é€€å®¤", "app.userlist.menu.removeConfirmation.desc": "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’二度ã¨ã‚»ãƒƒã‚·ãƒ§ãƒ³ã«å‚åŠ ã•ã›ãªã„", "app.userList.menu.muteUserAudio.label": "ユーザーをミュートã™ã‚‹", - "app.userList.menu.unmuteUserAudio.label": "ユーザーã®ãƒŸãƒ¥ãƒ¼ãƒˆã‚’外ã™", + "app.userList.menu.unmuteUserAudio.label": "ユーザーã®ãƒŸãƒ¥ãƒ¼ãƒˆã‚’解除ã™ã‚‹", "app.userList.userAriaLabel": "{0} {1} {2} ステータス {3}", "app.userList.menu.promoteUser.label": "モデレーターã«ã™ã‚‹", "app.userList.menu.demoteUser.label": "ãƒ“ãƒ¥ãƒ¼ã‚¢ãƒ¼ã«æˆ»ã™", @@ -92,9 +93,11 @@ "app.userList.userOptions.muteAllExceptPresenterLabel": "プレゼンター以外ã®å…¨ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’ミュートã™ã‚‹", "app.userList.userOptions.muteAllExceptPresenterDesc": "ã“ã®ä¼šè°ã®ãƒ—レゼンター以外ã®å…¨ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’ミュートã™ã‚‹", "app.userList.userOptions.unmuteAllLabel": "会è°ã®ãƒŸãƒ¥ãƒ¼ãƒˆã‚’オフã«ã™ã‚‹", - "app.userList.userOptions.unmuteAllDesc": "ã“ã®ä¼šè°ã®ãƒŸãƒ¥ãƒ¼ãƒˆã‚’外ã™", + "app.userList.userOptions.unmuteAllDesc": "ã“ã®ä¼šè°ã®ãƒŸãƒ¥ãƒ¼ãƒˆã‚’解除ã™ã‚‹", "app.userList.userOptions.lockViewersLabel": "ビューアーをãƒãƒƒã‚¯ã™ã‚‹", "app.userList.userOptions.lockViewersDesc": "ã“ã®ä¼šè°ã®å‚åŠ è€…å‘ã‘ã®ç‰¹å®šã®æ©Ÿèƒ½ã‚’ãƒãƒƒã‚¯ã™ã‚‹", + "app.userList.userOptions.connectionStatusLabel": "接続状æ³", + "app.userList.userOptions.connectionStatusDesc": "ãƒ¦ãƒ¼ã‚¶ã®æŽ¥ç¶šçŠ¶æ³ã‚’ã¿ã‚‹", "app.userList.userOptions.disableCam": "ビューアーã®ã‚¦ã‚§ãƒ–カメラã¯åˆ©ç”¨ã§ãã¾ã›ã‚“", "app.userList.userOptions.disableMic": "ビューアーã®ãƒžã‚¤ã‚¯ã¯åˆ©ç”¨ã§ãã¾ã›ã‚“", "app.userList.userOptions.disablePrivChat": "éžå…¬é–‹ãƒãƒ£ãƒƒãƒˆã¯åˆ©ç”¨ã§ãã¾ã›ã‚“", @@ -126,10 +129,13 @@ "app.meeting.meetingTimeRemaining": "会è°ã®æ®‹ã‚Šæ™‚間:{0}", "app.meeting.meetingTimeHasEnded": "終了時間ã§ã™ã€‚ã¾ã‚‚ãªã会è°ã‚’終了ã—ã¾ã™ã€‚", "app.meeting.endedMessage": "ãƒ›ãƒ¼ãƒ ã‚¹ã‚¯ãƒªãƒ¼ãƒ³ã«æˆ»ã‚Šã¾ã™ã€‚", - "app.meeting.alertMeetingEndsUnderOneMinute": "ã‚ã¨ï¼‘分ã§ä¼šè°ãŒçµ‚了ã—ã¾ã™ã€‚", - "app.meeting.alertBreakoutEndsUnderOneMinute": "ã‚ã¨ï¼‘分ã§ãƒ–レイクアウトセッションãŒçµ‚了ã—ã¾ã™ã€‚", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "会è°ã¯ã‚ã¨ä¸€åˆ†ã§çµ‚了ã—ã¾ã™ã€‚", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "会è°ã¯ã‚ã¨{0}分ã§çµ‚了ã—ã¾ã™ã€‚", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "ã“ã®å°ä¼šè°ã¯ã‚ã¨{0}分ã§çµ‚了ã—ã¾ã™ã€‚", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "ã“ã®å°ä¼šè°ã¯ã‚ã¨ä¸€åˆ†ã§çµ‚了ã—ã¾ã™ã€‚", "app.presentation.hide": "プレゼンテーションをéžè¡¨ç¤º", "app.presentation.notificationLabel": "ç¾åœ¨ã®ãƒ—レゼンテーション", + "app.presentation.downloadLabel": "ダウンãƒãƒ¼ãƒ‰", "app.presentation.slideContent": "スライドコンテンツ", "app.presentation.startSlideContent": "スライドコンテンツ開始", "app.presentation.endSlideContent": "スライドコンテンツ終了", @@ -174,6 +180,7 @@ "app.presentationUploder.rejectedError": "é¸æŠžãƒ•ã‚¡ã‚¤ãƒ«ãŒæ‹’å¦ã•れã¾ã—ãŸã€‚ファイル形å¼ã‚’確èªã—ã¦ãã ã•ã„。", "app.presentationUploder.upload.progress": "アップãƒãƒ¼ãƒ‰ä¸ ({0}%)", "app.presentationUploder.upload.413": "ファイルãŒå¤§ãã™ãŽã¾ã™ã€‚ã„ãã¤ã‹ã®ãƒ•ァイルã«åˆ†å‰²ã—ã¦ãã ã•ã„。", + "app.presentationUploder.genericError": "申ã—訳ã‚りã¾ã›ã‚“ãŒã€ä½•ã‹å•題ãŒã‚るよã†ã§ã™ã€‚", "app.presentationUploder.upload.408": "アップãƒãƒ¼ãƒ‰ãƒˆãƒ¼ã‚¯ãƒ³ã®è¦æ±‚ãŒæ™‚間切れã«ãªã‚Šã¾ã—ãŸã€‚", "app.presentationUploder.upload.404": "エラー404:無効ãªã‚¢ãƒƒãƒ—ãƒãƒ¼ãƒ‰ãƒˆãƒ¼ã‚¯ãƒ³", "app.presentationUploder.upload.401": "アップãƒãƒ¼ãƒ‰ãƒˆãƒ¼ã‚¯ãƒ³ã®è¦æ±‚ãŒå¤±æ•—ã—ã¾ã—ãŸã€‚", @@ -195,6 +202,13 @@ "app.presentationUploder.tableHeading.filename": "ファイルå", "app.presentationUploder.tableHeading.options": "オプション", "app.presentationUploder.tableHeading.status": "ステータス", + "app.presentationUploder.uploading": "{0} {1} アップãƒãƒ¼ãƒ‰ä¸", + "app.presentationUploder.uploadStatus": "{1}個ã®ã†ã¡{0}個をアップãƒãƒ¼ãƒ‰ã—ã¾ã—ãŸ", + "app.presentationUploder.completed": "{0}個をアップãƒãƒ¼ãƒ‰ã—ã¾ã—ãŸ", + "app.presentationUploder.item" : "資料", + "app.presentationUploder.itemPlural" : "資料", + "app.presentationUploder.clearErrors": "エラー消去", + "app.presentationUploder.clearErrorsDesc": "アップãƒãƒ¼ãƒ‰ã«å¤±æ•—ã—ãŸãƒ—レゼン資料を消去ã—ã¾ã™", "app.poll.pollPaneTitle": "投票", "app.poll.quickPollTitle": "簡易投票", "app.poll.hidePollDesc": "投票メニュー画é¢ã‚’éš ã™", @@ -240,6 +254,7 @@ "app.connectingMessage": "接続ä¸...", "app.waitingMessage": "接続ãŒåˆ‡ã‚Œã¾ã—ãŸã€‚ {0} ç§’ã§å†æŽ¥ç¶šã—ã¾ã™ ...", "app.retryNow": "リトライ", + "app.muteWarning.label": "自分ã®ãƒŸãƒ¥ãƒ¼ãƒˆã‚’解除ã™ã‚‹ãŸã‚{0}をクリック", "app.navBar.settingsDropdown.optionsLabel": "オプション", "app.navBar.settingsDropdown.fullscreenLabel": "全画é¢è¡¨ç¤ºã«åˆ‡æ›¿", "app.navBar.settingsDropdown.settingsLabel": "è¨å®š", @@ -267,7 +282,7 @@ "app.leaveConfirmation.confirmLabel": "退室", "app.leaveConfirmation.confirmDesc": "自分を会è°ã‹ã‚‰ãƒã‚°ã‚¢ã‚¦ãƒˆã•ã›ã‚‹", "app.endMeeting.title": "会è°ã‚’終了ã™ã‚‹", - "app.endMeeting.description": "ã“ã®ä¼šè°ã‚’終了ã—ã¾ã™ã‹ï¼Ÿ", + "app.endMeeting.description": "本当ã«ã“ã®ä¼šè°ã‚’終了(ã™ã¹ã¦ã®å‚åŠ è€…ã®æŽ¥ç¶šã‚’åˆ‡æ–)ã—ã¦ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ", "app.endMeeting.yesLabel": "ã¯ã„", "app.endMeeting.noLabel": "ã„ã„ãˆ", "app.about.title": "è£½å“æƒ…å ±", @@ -288,10 +303,6 @@ "app.screenshare.screenShareLabel" : "ç”»é¢å…±æœ‰", "app.submenu.application.applicationSectionTitle": "アプリケーション", "app.submenu.application.animationsLabel": "アニメーション", - "app.submenu.application.audioAlertLabel": "ãƒãƒ£ãƒƒãƒˆã®éŸ³å£°é€šçŸ¥", - "app.submenu.application.pushAlertLabel": "ãƒãƒ£ãƒƒãƒˆã®ãƒãƒƒãƒ—アップ通知", - "app.submenu.application.userJoinAudioAlertLabel": "ユーザå‚åŠ ã®éŸ³å£°é€šçŸ¥", - "app.submenu.application.userJoinPushAlertLabel": "ユーザå‚åŠ ã®ãƒãƒƒãƒ—アップ", "app.submenu.application.fontSizeControlLabel": "フォントサイズ", "app.submenu.application.increaseFontBtnLabel": "アプリケーションã®ãƒ•ォントサイズを大ããã™ã‚‹", "app.submenu.application.decreaseFontBtnLabel": "アプリケーションã®ãƒ•ォントサイズをå°ã•ãã™ã‚‹", @@ -299,6 +310,12 @@ "app.submenu.application.languageLabel": "アプリケーション言語", "app.submenu.application.languageOptionLabel": "è¨€èªžã‚’é¸æŠž", "app.submenu.application.noLocaleOptionLabel": "アクティブãªãƒã‚±ãƒ¼ãƒ«ãŒã‚りã¾ã›ã‚“", + "app.submenu.notification.SectionTitle": "通知", + "app.submenu.notification.Desc": "何をã©ã®ã‚ˆã†ã«é€šçŸ¥ã™ã‚‹ã‹ã‚’è¨å®šã—ã¦ãã ã•ã„。", + "app.submenu.notification.audioAlertLabel": "音声通知", + "app.submenu.notification.pushAlertLabel": "ãƒãƒƒãƒ—アップ通知", + "app.submenu.notification.messagesLabel": "ãƒãƒ£ãƒƒãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸", + "app.submenu.notification.userJoinLabel": "ユーザã®å‚åŠ ", "app.submenu.audio.micSourceLabel": "マイクã®ã‚½ãƒ¼ã‚¹", "app.submenu.audio.speakerSourceLabel": "スピーカーã®ã‚½ãƒ¼ã‚¹", "app.submenu.audio.streamVolumeLabel": "音声ストリームã®éŸ³é‡", @@ -322,6 +339,10 @@ "app.settings.dataSavingTab.screenShare": "デスクトップ共有を有効ã«ã™ã‚‹", "app.settings.dataSavingTab.description": "通信é‡ã‚’減らã™ãŸã‚è¨å®šã‚’変更ã—ã¦ãã ã•ã„", "app.settings.save-notification.label": "è¨å®šãŒä¿å˜ã•れã¾ã—ãŸ", + "app.statusNotifier.lowerHands": "手をãŠã‚ã™", + "app.statusNotifier.raisedHandsTitle": "手を上ã’ã‚‹", + "app.statusNotifier.raisedHandDesc": "{0}äººãŒæ‰‹ã‚’上ã’ã¾ã—ãŸ", + "app.statusNotifier.and": "ã¨", "app.switch.onLabel": "å…¥", "app.switch.offLabel": "切", "app.talkingIndicator.ariaMuteDesc" : "ユーザをミュートã—ã¾ã™", @@ -484,8 +505,10 @@ "app.userList.guest.denyEveryone": "全員を拒å¦ã™ã‚‹", "app.userList.guest.pendingUsers": "{0} ä¿ç•™ä¸ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼", "app.userList.guest.pendingGuestUsers": "{0} ä¿ç•™ä¸ã®ã‚²ã‚¹ãƒˆãƒ¦ãƒ¼ã‚¶ãƒ¼", - "app.userList.guest.pendingGuestAlert": "セッションã«å‚åŠ ã—ã€è¨±å¯ã‚’å¾…ã£ã¦ã„ã¾ã™ã€‚", + "app.userList.guest.pendingGuestAlert": "ã¯ã‚»ãƒƒã‚·ãƒ§ãƒ³ã«å‚åŠ ã—ã€è¨±å¯ã‚’å¾…ã£ã¦ã„ã¾ã™ã€‚", "app.userList.guest.rememberChoice": "é¸æŠžã‚’è¨˜æ†¶ã•ã›ã‚‹", + "app.userList.guest.acceptLabel": "承èª", + "app.userList.guest.denyLabel": "å´ä¸‹", "app.user-info.title": "ディレクトリ検索", "app.toast.breakoutRoomEnded": "å°ä¼šè°ãŒçµ‚了ã—ã¾ã—ãŸã€‚音声ã§ã‚‚ã†ä¸€åº¦å‚åŠ ã—ã¦ãã ã•ã„。", "app.toast.chat.public": "æ–°ã—ã„グループãƒãƒ£ãƒƒãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸", @@ -497,9 +520,10 @@ "app.toast.meetingMuteOff.label": "会è°ã®ãƒŸãƒ¥ãƒ¼ãƒˆã‚’解除ã—ã¾ã—ãŸ", "app.notification.recordingStart": "ã“ã®ã‚»ãƒƒã‚·ãƒ§ãƒ³ã¯ç¾åœ¨éŒ²ç”»ä¸ã§ã™", "app.notification.recordingStop": "ã“ã®ã‚»ãƒƒã‚·ãƒ§ãƒ³ã¯éŒ²ç”»ã•れã¦ã„ã¾ã›ã‚“", - "app.notification.recordingPaused": "録画ãŒçµ‚了ã—ã¾ã—ãŸ", + "app.notification.recordingPaused": "ã“ã®ã‚»ãƒƒã‚·ãƒ§ãƒ³ã¯ã‚‚ã†éŒ²ç”»ã•れã¦ã„ã¾ã›ã‚“", "app.notification.recordingAriaLabel": "録画時間", "app.notification.userJoinPushAlert": "{0} ãŒã‚»ãƒƒã‚·ãƒ§ãƒ³ã«å‚åŠ ã—ã¾ã—ãŸ", + "app.submenu.notification.raiseHandLabel": "挙手", "app.shortcut-help.title": "ã‚ーボードショートカット", "app.shortcut-help.accessKeyNotAvailable": "使用ã§ããªã„アクセスã‚ー", "app.shortcut-help.comboLabel": "コンボ", @@ -533,6 +557,12 @@ "app.lock-viewers.button.cancel": "ã‚ャンセル", "app.lock-viewers.locked": "ãƒãƒƒã‚¯", "app.lock-viewers.unlocked": "ãƒãƒƒã‚¯è§£é™¤", + "app.connection-status.ariaTitle": "接続状æ³ãƒ¢ãƒ¼ãƒ€ãƒ«", + "app.connection-status.title": "接続状æ³", + "app.connection-status.description": "ãƒ¦ãƒ¼ã‚¶ã®æŽ¥ç¶šçŠ¶æ³ã‚’ã¿ã‚‹", + "app.connection-status.empty": "ç¾åœ¨ã®ã¨ã“ã‚æŽ¥ç¶šã«é–¢ã™ã‚‹å•題ã¯ã¿ã¨ã‚られã¾ã›ã‚“", + "app.connection-status.more": "æ›´ã«è¦‹ã‚‹", + "app.connection-status.offline": "オフライン", "app.recording.startTitle": "録画開始", "app.recording.stopTitle": "éŒ²ç”»ä¸€æ™‚åœæ¢", "app.recording.resumeTitle": "録画å†é–‹", @@ -540,10 +570,17 @@ "app.recording.stopDescription": "録画をæ¢ã‚ã¾ã™ã‹ï¼Ÿ å†åº¦ãƒœã‚¿ãƒ³ã‚’クリックã™ã‚‹ã¨å†é–‹ã—ã¾ã™", "app.videoPreview.cameraLabel": "カメラ", "app.videoPreview.profileLabel": "å“質", + "app.videoPreview.quality.low": "低", + "app.videoPreview.quality.medium": "ä¸", + "app.videoPreview.quality.high": "高", + "app.videoPreview.quality.hd": "HD", "app.videoPreview.cancelLabel": "ã‚ャンセル", "app.videoPreview.closeLabel": "é–‰ã˜ã‚‹", "app.videoPreview.findingWebcamsLabel": "ウェブカメラå–å¾—ä¸", "app.videoPreview.startSharingLabel": "共有を開始", + "app.videoPreview.stopSharingLabel": "å…±æœ‰ã‚’åœæ¢", + "app.videoPreview.stopSharingAllLabel": "å…¨ã¦ã®å…±æœ‰ã‚’åœæ¢", + "app.videoPreview.sharedCameraLabel": "ã‚«ãƒ¡ãƒ©ã¯æ—¢ã«å…±æœ‰ã•れã¦ã„ã¾ã™", "app.videoPreview.webcamOptionLabel": "ã‚¦ã‚§ãƒ–ã‚«ãƒ¡ãƒ©ã‚’é¸æŠž", "app.videoPreview.webcamPreviewLabel": "ウェブカメラã®ãƒ—レビュー", "app.videoPreview.webcamSettingsTitle": "ウェブカメラè¨å®š", @@ -573,19 +610,8 @@ "app.video.videoMenuDesc": "ビデオメニューã®ãƒ‰ãƒãƒƒãƒ—ダウンを開ã", "app.video.chromeExtensionError": "インストールã—ã¦ãã ã•ã„", "app.video.chromeExtensionErrorLink": "Chromeã®æ‹¡å¼µæ©Ÿèƒ½", - "app.video.stats.title": "接続状態", - "app.video.stats.packetsReceived": "パケットå—ä¿¡", - "app.video.stats.packetsSent": "パケットé€ä¿¡", - "app.video.stats.packetsLost": "エラーパケット", - "app.video.stats.bitrate": "ビットレート", - "app.video.stats.lostPercentage": "ç·æå¤±çŽ‡", - "app.video.stats.lostRecentPercentage": "æœ€æ–°ã®æå¤±çŽ‡", - "app.video.stats.dimensions": "è§£åƒåº¦", - "app.video.stats.codec": "コーデック", - "app.video.stats.decodeDelay": "é…延(ディコード)", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "エンコードã®ä½¿ç”¨çжæ³", - "app.video.stats.currentDelay": "é…延(最新)", + "app.video.pagination.prevPage": "å‰ã®ãƒ“デオを見る", + "app.video.pagination.nextPage": "次ã®ãƒ“デオを見る", "app.fullscreenButton.label": "{0}を全画é¢ã«åˆ‡ã‚Šæ›¿ãˆã‚‹", "app.deskshare.iceConnectionStateError": "ç”»é¢å…±æœ‰ã®æŽ¥ç¶šã«å¤±æ•—ã—ã¾ã—㟠(ICE error 1108)", "app.sfu.mediaServerConnectionError2000": "メディアサーãƒãƒ¼ã«æŽ¥ç¶šã§ãã¾ã›ã‚“ (error 2000)", @@ -599,6 +625,7 @@ "app.sfu.noAvailableCodec2203": "é©åˆ‡ãªã‚³ãƒ¼ãƒ‡ãƒƒã‚¯ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—㟠(error 2203)", "app.meeting.endNotification.ok.label": "OK", "app.whiteboard.annotations.poll": "æŠ•ç¥¨çµæžœãŒå…¬é–‹ã•れã¾ã—ãŸ", + "app.whiteboard.annotations.pollResult": "æŠ•ç¥¨çµæžœ", "app.whiteboard.toolbar.tools": "ツール", "app.whiteboard.toolbar.tools.hand": "パン", "app.whiteboard.toolbar.tools.pencil": "ペン", @@ -679,7 +706,7 @@ "app.externalVideo.autoPlayWarning": "動画をå†ç”Ÿã—ã¦ãƒ¡ãƒ‡ã‚£ã‚¢ã®åŒæœŸã‚’有効ã«ã™ã‚‹", "app.network.connection.effective.slow": "接続ã®å•題ãŒç™ºç”Ÿã—ã¾ã—ãŸ", "app.network.connection.effective.slow.help": "詳ã—ã„æƒ…å ±", - "app.externalVideo.noteLabel": "注æ„:インターãƒãƒƒãƒˆä¸Šã®å‹•ç”»ã¯éŒ²ç”»ã•れã¾ã›ã‚“。YouTube, Vimeo, Instructure Media, Twitch, Daily Motionã®URLãŒã‚µãƒãƒ¼ãƒˆã•れã¦ã„ã¾ã™", + "app.externalVideo.noteLabel": "注æ„:インターãƒãƒƒãƒˆä¸Šã®å‹•ç”»ã¯éŒ²ç”»ã§ãã¾ã›ã‚“。YouTube, Vimeo, Instructure Media, Twitch, Daily Motion, ãŠã‚ˆã³ãƒ¡ãƒ‡ã‚£ã‚¢ãƒ•ァイルã®URL(例ãˆã°https://example.com/xy.mp4)ãŒã‚µãƒãƒ¼ãƒˆã•れã¦ã„ã¾ã™", "app.actionsBar.actionsDropdown.shareExternalVideo": "動画を共有ã™ã‚‹", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "å‹•ç”»ã®å…±æœ‰åœæ¢", "app.iOSWarning.label": "iOS 12.2ã¾ãŸã¯ãれ以é™ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«ã‚¢ãƒƒãƒ—グレードã—ã¦ãã ã•ã„", diff --git a/bigbluebutton-html5/private/locales/ka_GE.json b/bigbluebutton-html5/private/locales/ka.json similarity index 95% rename from bigbluebutton-html5/private/locales/ka_GE.json rename to bigbluebutton-html5/private/locales/ka.json index df4977d517a48d2f34d4e6a0f60dca3086d816ee..08a580caf36e35c3723f949f0ddcbb32559217c0 100644 --- a/bigbluebutton-html5/private/locales/ka_GE.json +++ b/bigbluebutton-html5/private/locales/ka.json @@ -50,10 +50,10 @@ "app.note.title": "სáƒáƒ”რთრჩáƒáƒœáƒáƒ¬áƒ”რები", "app.note.label": "ჩáƒáƒœáƒáƒ¬áƒ”რი", "app.note.hideNoteLabel": "ჩáƒáƒœáƒáƒ¬áƒ”რის დáƒáƒ¤áƒáƒ ვáƒ", + "app.note.tipLabel": "დáƒáƒ¬áƒ™áƒáƒžáƒ”თ Esc რედáƒáƒ¥áƒ¢áƒ˜áƒ ების ზáƒáƒšáƒ˜áƒ¡ ფáƒáƒ™áƒ£áƒ¡áƒ˜áƒ ებისთვის", "app.user.activityCheck": "მáƒáƒ›áƒ®áƒ›áƒáƒ ებლის áƒáƒ¥áƒ¢áƒ˜áƒ•áƒáƒ‘ის შემáƒáƒ¬áƒ›áƒ”ბáƒ", "app.user.activityCheck.label": "შეáƒáƒ›áƒáƒ¬áƒ›áƒ”თ, ისევ ესწრებრთუ áƒáƒ რმáƒáƒ›áƒ®áƒ›áƒáƒ ებელი ({0}) შეხვედრáƒáƒ¡", "app.user.activityCheck.check": "შემáƒáƒ¬áƒ›áƒ”ბáƒ", - "app.note.tipLabel": "დáƒáƒ¬áƒ™áƒáƒžáƒ”თ Esc რედáƒáƒ¥áƒ¢áƒ˜áƒ ების ზáƒáƒšáƒ˜áƒ¡ ფáƒáƒ™áƒ£áƒ¡áƒ˜áƒ ებისთვის", "app.userList.usersTitle": "მáƒáƒ›áƒ®áƒ›áƒáƒ ებლები", "app.userList.participantsTitle": "მáƒáƒœáƒáƒ¬áƒ˜áƒšáƒ”ები", "app.userList.messagesTitle": "შეტყáƒáƒ‘ინებები", @@ -121,8 +121,6 @@ "app.meeting.meetingTimeRemaining": "შეხვედრისთვის დáƒáƒ ჩენილი დრáƒ: {0}", "app.meeting.meetingTimeHasEnded": "დრრáƒáƒ›áƒáƒ˜áƒ¬áƒ£áƒ áƒ. სესირმáƒáƒšáƒ” დáƒáƒ˜áƒ®áƒ£áƒ ებáƒ", "app.meeting.endedMessage": "თქვენ დáƒáƒ‘რუნდებით სáƒáƒ¬áƒ§áƒ˜áƒ¡ ეკრáƒáƒœáƒ–ე", - "app.meeting.alertMeetingEndsUnderOneMinute": "შეხვედრრერთ წუთში დáƒáƒ¡áƒ ულდებáƒ", - "app.meeting.alertBreakoutEndsUnderOneMinute": "გáƒáƒœáƒ®áƒ˜áƒšáƒ•ის áƒáƒ—áƒáƒ®áƒ˜ ერთ წუთში დáƒáƒ˜áƒ®áƒ£áƒ ებáƒ", "app.presentation.hide": "პრეზენტáƒáƒªáƒ˜áƒ˜áƒ¡ დáƒáƒ›áƒáƒšáƒ•áƒ", "app.presentation.notificationLabel": "მიმდინáƒáƒ ე პრეზენტáƒáƒªáƒ˜áƒ", "app.presentation.slideContent": "სლáƒáƒ˜áƒ“ის შინáƒáƒáƒ სი", @@ -194,7 +192,7 @@ "app.poll.quickPollInstruction": "შეáƒáƒ ჩიეთ ქვემáƒáƒ— მáƒáƒªáƒ”მული ვáƒáƒ იáƒáƒœáƒ¢áƒ˜ გáƒáƒ›áƒáƒ™áƒ˜áƒ—ხვის დáƒáƒ¡áƒáƒ¬áƒ§áƒ”ბáƒáƒ“", "app.poll.customPollLabel": "თქვენი გáƒáƒ›áƒáƒ™áƒ˜áƒ—ხვáƒ", "app.poll.startCustomLabel": "თქვენი გáƒáƒ›áƒáƒ™áƒ˜áƒ—ხვის დáƒáƒ¬áƒ§áƒ”ბáƒ", - "app.poll.activePollInstruction": "დáƒáƒ¢áƒáƒ•ეთ ეს პáƒáƒœáƒ”ლი ღიáƒ, რáƒáƒ› ნáƒáƒ®áƒáƒ— თქვენი გáƒáƒ›áƒáƒ™áƒ˜áƒ—ხვების პáƒáƒ¡áƒ£áƒ®áƒ”ბი áƒáƒœáƒšáƒáƒ˜áƒœ რეჟიმში. რáƒáƒ“ესáƒáƒª მზáƒáƒ“ იქნებით, áƒáƒ˜áƒ ჩიეთ \"კენáƒáƒ˜áƒ¡áƒ§áƒ ის შედეგების გáƒáƒ›áƒáƒ¥áƒ•ეყნებáƒ\", შედეგების გáƒáƒ›áƒáƒ¥áƒ•ეყნებისრდრგáƒáƒ›áƒáƒ™áƒ˜áƒ—ხვის დáƒáƒ¡áƒ ულების ", + "app.poll.activePollInstruction": "დáƒáƒ¢áƒáƒ•ეთ ეს პáƒáƒœáƒ”ლი ღიáƒ, რáƒáƒ› ნáƒáƒ®áƒáƒ— თქვენი გáƒáƒ›áƒáƒ™áƒ˜áƒ—ხვების პáƒáƒ¡áƒ£áƒ®áƒ”ბი áƒáƒœáƒšáƒáƒ˜áƒœ რეჟიმში. რáƒáƒ“ესáƒáƒª მზáƒáƒ“ იქნებით, áƒáƒ˜áƒ ჩიეთ 'კენáƒáƒ˜áƒ¡áƒ§áƒ ის შედეგების გáƒáƒ›áƒáƒ¥áƒ•ეყნებáƒ', შედეგების გáƒáƒ›áƒáƒ¥áƒ•ეყნებისრდრგáƒáƒ›áƒáƒ™áƒ˜áƒ—ხვის დáƒáƒ¡áƒ ულების ", "app.poll.publishLabel": "გáƒáƒ›áƒáƒ™áƒ˜áƒ—ხვის შედეგების გáƒáƒ›áƒáƒ¥áƒ•ეყნებáƒ", "app.poll.backLabel": "დáƒáƒ£áƒ‘რუნდით გáƒáƒ›áƒáƒ™áƒ˜áƒ—ხვის ვáƒáƒ იáƒáƒœáƒ¢áƒ”ბს", "app.poll.closeLabel": "დáƒáƒ®áƒ£áƒ ვáƒ", @@ -259,7 +257,6 @@ "app.leaveConfirmation.confirmLabel": "დáƒáƒ¢áƒáƒ•ებáƒ", "app.leaveConfirmation.confirmDesc": "გáƒáƒ›áƒáƒ§áƒáƒ•ხáƒáƒ თ შეხვედრიდáƒáƒœ", "app.endMeeting.title": "შეხვედრის დáƒáƒ¡áƒ ულებáƒ", - "app.endMeeting.description": "დáƒáƒ წმუნებული ხáƒáƒ თ, რáƒáƒ› გსურთ áƒáƒ› სესიის დáƒáƒ¡áƒ ულებáƒ?", "app.endMeeting.yesLabel": "დიáƒáƒ®", "app.endMeeting.noLabel": "áƒáƒ áƒ", "app.about.title": "შესáƒáƒ®áƒ”ბ", @@ -280,10 +277,6 @@ "app.screenshare.screenShareLabel" : "ეკრáƒáƒœáƒ˜áƒ¡ გáƒáƒ–იáƒáƒ ებáƒ", "app.submenu.application.applicationSectionTitle": "áƒáƒžáƒšáƒ˜áƒ™áƒáƒªáƒ˜áƒ", "app.submenu.application.animationsLabel": "áƒáƒœáƒ˜áƒ›áƒáƒªáƒ˜áƒ", - "app.submenu.application.audioAlertLabel": "áƒáƒ£áƒ“ირშეტყáƒáƒ‘ინებები სáƒáƒ¡áƒáƒ£áƒ‘რáƒáƒ¡áƒ—ვის", - "app.submenu.application.pushAlertLabel": "áƒáƒ›áƒáƒªáƒ£áƒ ებული შეტყáƒáƒ‘ინებრსáƒáƒ¡áƒáƒ£áƒ‘რáƒáƒ¡áƒ—ვის", - "app.submenu.application.userJoinAudioAlertLabel": "áƒáƒ£áƒ“ირშეტყáƒáƒ‘ინებრმáƒáƒ›áƒ®áƒ›áƒáƒ ებლის შემáƒáƒ”რთებისáƒáƒ¡", - "app.submenu.application.userJoinPushAlertLabel": "áƒáƒ›áƒáƒªáƒ£áƒ ებული შეტყáƒáƒ‘ინებრáƒáƒ®áƒáƒšáƒ˜ მáƒáƒ›áƒ®áƒ›áƒáƒ ებლის შემáƒáƒ”რთებისáƒáƒ¡", "app.submenu.application.fontSizeControlLabel": "ფáƒáƒœáƒ¢áƒ˜áƒ¡ ზáƒáƒ›áƒ", "app.submenu.application.increaseFontBtnLabel": "áƒáƒžáƒšáƒ˜áƒ™áƒáƒªáƒ˜áƒ˜áƒ¡áƒ—ვის ფáƒáƒœáƒ¢áƒ˜áƒ¡ ზáƒáƒ›áƒ˜áƒ¡ გáƒáƒ–რდáƒ", "app.submenu.application.decreaseFontBtnLabel": "áƒáƒžáƒšáƒ˜áƒ™áƒáƒªáƒ˜áƒ˜áƒ¡áƒ—ვის ფáƒáƒœáƒ¢áƒ˜áƒ¡ ზáƒáƒ›áƒ˜áƒ¡ შემცირებáƒ", @@ -340,25 +333,25 @@ "app.actionsBar.actionsDropdown.takePresenterDesc": "áƒáƒ¥áƒªáƒ˜áƒ”თ სáƒáƒ™áƒ£áƒ—áƒáƒ ი თáƒáƒ•ი áƒáƒ®áƒáƒš მáƒáƒ›áƒ®áƒ¡áƒ”ნებლáƒáƒ“", "app.actionsBar.emojiMenu.statusTriggerLabel": "სტáƒáƒ¢áƒ£áƒ¡áƒ˜áƒ¡ დáƒáƒ§áƒ”ნებáƒ", "app.actionsBar.emojiMenu.awayLabel": "გáƒáƒ¡áƒ£áƒšáƒ˜áƒ", - "app.actionsBar.emojiMenu.awayDesc": "შეცვáƒáƒšáƒ”თ სტáƒáƒ¢áƒ£áƒ¡áƒ˜ რáƒáƒ’áƒáƒ ც \"გáƒáƒ¡áƒ£áƒšáƒ˜áƒ\"", + "app.actionsBar.emojiMenu.awayDesc": "შეცვáƒáƒšáƒ”თ სტáƒáƒ¢áƒ£áƒ¡áƒ˜ რáƒáƒ’áƒáƒ ც 'გáƒáƒ¡áƒ£áƒšáƒ˜áƒ'", "app.actionsBar.emojiMenu.raiseHandLabel": "ხელის áƒáƒ¬áƒ”ვáƒ", "app.actionsBar.emojiMenu.raiseHandDesc": "áƒáƒ˜áƒ¬áƒ˜áƒ”თ ხელი კითხვის დáƒáƒ¡áƒáƒ¡áƒ›áƒ”ლáƒáƒ“", "app.actionsBar.emojiMenu.neutralLabel": "გáƒáƒ“áƒáƒ£áƒ¬áƒ§áƒ•ეტელი", - "app.actionsBar.emojiMenu.neutralDesc": "áƒáƒ¥áƒªáƒ˜áƒ”თ თქვენი სტáƒáƒ¢áƒ£áƒ¡áƒ˜ \"გáƒáƒ“áƒáƒ£áƒ¬áƒ§áƒ•ეტლáƒáƒ“\"", + "app.actionsBar.emojiMenu.neutralDesc": "áƒáƒ¥áƒªáƒ˜áƒ”თ თქვენი სტáƒáƒ¢áƒ£áƒ¡áƒ˜ 'გáƒáƒ“áƒáƒ£áƒ¬áƒ§áƒ•ეტლáƒáƒ“'", "app.actionsBar.emojiMenu.confusedLabel": "დáƒáƒ›áƒáƒ ცხვებული", - "app.actionsBar.emojiMenu.confusedDesc": "áƒáƒ¥áƒªáƒ˜áƒ”თ თქვენი სტáƒáƒ¢áƒ£áƒ¡áƒ˜ \"დáƒáƒ›áƒáƒ ცხვებულáƒáƒ“\"", + "app.actionsBar.emojiMenu.confusedDesc": "áƒáƒ¥áƒªáƒ˜áƒ”თ თქვენი სტáƒáƒ¢áƒ£áƒ¡áƒ˜ 'დáƒáƒ›áƒáƒ ცხვებულáƒáƒ“'", "app.actionsBar.emojiMenu.sadLabel": "მáƒáƒ¬áƒ§áƒ”ნილი", - "app.actionsBar.emojiMenu.sadDesc": "áƒáƒ¥áƒªáƒ˜áƒ”თ თქვენი სტáƒáƒ¢áƒ£áƒ¡áƒ˜ \"მáƒáƒ¬áƒ§áƒ”ნილáƒáƒ“\"", + "app.actionsBar.emojiMenu.sadDesc": "áƒáƒ¥áƒªáƒ˜áƒ”თ თქვენი სტáƒáƒ¢áƒ£áƒ¡áƒ˜ 'მáƒáƒ¬áƒ§áƒ”ნილáƒáƒ“'", "app.actionsBar.emojiMenu.happyLabel": "ბედნიერი", - "app.actionsBar.emojiMenu.happyDesc": "áƒáƒ¥áƒªáƒ˜áƒ”თ თქვენი სტáƒáƒ¢áƒ£áƒ¡áƒ˜ \"ბედნიერáƒáƒ“\"", + "app.actionsBar.emojiMenu.happyDesc": "áƒáƒ¥áƒªáƒ˜áƒ”თ თქვენი სტáƒáƒ¢áƒ£áƒ¡áƒ˜ 'ბედნიერáƒáƒ“'", "app.actionsBar.emojiMenu.noneLabel": "სტáƒáƒ¢áƒ£áƒ¡áƒ˜áƒ¡ წáƒáƒ¨áƒšáƒ", "app.actionsBar.emojiMenu.noneDesc": "წáƒáƒ¨áƒáƒšáƒ”თ თქვენი სტáƒáƒ¢áƒ£áƒ¡áƒ˜", "app.actionsBar.emojiMenu.applauseLabel": "áƒáƒžáƒšáƒáƒ“ისმენტები", - "app.actionsBar.emojiMenu.applauseDesc": "áƒáƒ¥áƒªáƒ˜áƒ”თ თქვენი სტáƒáƒ¢áƒ£áƒ¡áƒ˜ \"áƒáƒžáƒšáƒáƒ“ისმენტებáƒáƒ“\"", + "app.actionsBar.emojiMenu.applauseDesc": "áƒáƒ¥áƒªáƒ˜áƒ”თ თქვენი სტáƒáƒ¢áƒ£áƒ¡áƒ˜ 'áƒáƒžáƒšáƒáƒ“ისმენტებáƒáƒ“'", "app.actionsBar.emojiMenu.thumbsUpLabel": "მáƒáƒ¬áƒáƒœáƒ”ბáƒ", - "app.actionsBar.emojiMenu.thumbsUpDesc": "áƒáƒ¥áƒªáƒ˜áƒ”თ თქვენი სტáƒáƒ¢áƒ£áƒ¡áƒ˜ \"მáƒáƒ¬áƒáƒœáƒ”ბáƒáƒ“\" ", + "app.actionsBar.emojiMenu.thumbsUpDesc": "áƒáƒ¥áƒªáƒ˜áƒ”თ თქვენი სტáƒáƒ¢áƒ£áƒ¡áƒ˜ 'მáƒáƒ¬áƒáƒœáƒ”ბáƒáƒ“'", "app.actionsBar.emojiMenu.thumbsDownLabel": "უკმáƒáƒ§áƒáƒ¤áƒ˜áƒšáƒ”ბáƒ", - "app.actionsBar.emojiMenu.thumbsDownDesc": "áƒáƒ¥áƒªáƒ˜áƒ”თ თვენი სტáƒáƒ¢áƒ£áƒ¡áƒ˜ \"უკმáƒáƒ§áƒáƒ¤áƒ˜áƒšáƒáƒ“\"", + "app.actionsBar.emojiMenu.thumbsDownDesc": "áƒáƒ¥áƒªáƒ˜áƒ”თ თვენი სტáƒáƒ¢áƒ£áƒ¡áƒ˜ 'უკმáƒáƒ§áƒáƒ¤áƒ˜áƒšáƒáƒ“'", "app.actionsBar.currentStatusDesc": "მიმდინáƒáƒ ე სტáƒáƒ¢áƒ£áƒ¡áƒ˜ {0}", "app.actionsBar.captions.start": "სუბტიტრების ნáƒáƒ®áƒ•ის დáƒáƒ¬áƒ§áƒ”ბáƒ", "app.actionsBar.captions.stop": "სუბტიტრების ნáƒáƒ®áƒ•ის შეწყვეტáƒ", @@ -565,19 +558,6 @@ "app.video.videoMenuDesc": "გáƒáƒ®áƒ¡áƒ”ნით ვიდეáƒáƒ¡ ჩáƒáƒ›áƒáƒ¡áƒáƒ¨áƒšáƒ”ლი მენიუ", "app.video.chromeExtensionError": "თქვენ უნდრდáƒáƒáƒ˜áƒœáƒ¡áƒ¢áƒáƒšáƒ˜áƒ áƒáƒ—", "app.video.chromeExtensionErrorLink": "Chrome-ის ეს გáƒáƒ¤áƒáƒ თáƒáƒ”ბáƒ", - "app.video.stats.title": "კáƒáƒ•შირის სტáƒáƒ¢áƒ˜áƒ¡áƒ¢áƒ˜áƒ™áƒ", - "app.video.stats.packetsReceived": "მიღებული პáƒáƒ™áƒ”ტები", - "app.video.stats.packetsSent": "გáƒáƒ’ზáƒáƒ•ნილი პáƒáƒ™áƒ”ტები", - "app.video.stats.packetsLost": "დáƒáƒ™áƒáƒ გული პáƒáƒ™áƒ”ტები", - "app.video.stats.bitrate": "Bitrate", - "app.video.stats.lostPercentage": "დáƒáƒ™áƒáƒ გული პრáƒáƒªáƒ”ნტი ჯáƒáƒ›áƒ£áƒ áƒáƒ“", - "app.video.stats.lostRecentPercentage": "ბáƒáƒšáƒáƒ¡ დáƒáƒ™áƒáƒ გული პრáƒáƒªáƒ”ნტი", - "app.video.stats.dimensions": "გáƒáƒœáƒ–áƒáƒ›áƒ˜áƒšáƒ”ბáƒ", - "app.video.stats.codec": "კáƒáƒ“ეკი", - "app.video.stats.decodeDelay": "დეკáƒáƒ“ირების შეყáƒáƒ•ნებáƒ", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "კáƒáƒ“ირების/დáƒáƒ¨áƒ˜áƒ¤áƒ ვის გáƒáƒ›áƒáƒ§áƒ”ნებáƒ", - "app.video.stats.currentDelay": "მიმდინáƒáƒ ე შეყáƒáƒ•ნებáƒ", "app.fullscreenButton.label": "გáƒáƒ›áƒáƒ˜áƒ¢áƒáƒœáƒ” {0} სრულ ეკრáƒáƒœáƒ–ე", "app.deskshare.iceConnectionStateError": "ეკრáƒáƒœáƒ˜áƒ¡ გáƒáƒ–ირებისáƒáƒ¡ კáƒáƒ•შირი შეწყდრ(ICE შეცდáƒáƒ›áƒ 1108)", "app.sfu.mediaServerConnectionError2000": "ვერხერხდებრმედირსერვერთáƒáƒœ დáƒáƒ™áƒáƒ•შირებრ(შეცდáƒáƒ›áƒ 2000)", @@ -590,7 +570,6 @@ "app.sfu.invalidSdp2202":"Client generated an invalid media request (SDP error 2202)", "app.sfu.noAvailableCodec2203": "სერვერმრვერიპáƒáƒ•რშესáƒáƒ‘áƒáƒ›áƒ˜áƒ¡áƒ˜ კáƒáƒ“ეკი (შეცდáƒáƒ›áƒ 2203)", "app.meeting.endNotification.ok.label": "OK", - "app.whiteboard.annotations.poll": "გáƒáƒ›áƒáƒ™áƒ˜áƒ—ხვის შედეგები გáƒáƒ›áƒáƒ¥áƒ•ეყნებულიáƒ", "app.whiteboard.toolbar.tools": "ხელსáƒáƒ¬áƒ§áƒáƒ”ბი", "app.whiteboard.toolbar.tools.hand": "კáƒáƒšáƒáƒ›áƒ˜", "app.whiteboard.toolbar.tools.pencil": "ფáƒáƒœáƒ¥áƒáƒ ი", @@ -671,13 +650,12 @@ "app.externalVideo.autoPlayWarning": "მედირსინქრáƒáƒœáƒ˜áƒ–áƒáƒªáƒ˜áƒ˜áƒ¡ უზრუნველსáƒáƒ§áƒáƒ¤áƒáƒ“ გáƒáƒ£áƒ¨áƒ•ით ვიდეáƒ", "app.network.connection.effective.slow": "ჩვენ შევáƒáƒ›áƒ©áƒœáƒ˜áƒ”თ კáƒáƒ•შირის პრáƒáƒ‘ლემáƒ", "app.network.connection.effective.slow.help": "მეტი ინფáƒáƒ მáƒáƒªáƒ˜áƒ", - "app.externalVideo.noteLabel": "შენიშვნáƒ: გáƒáƒ–იáƒáƒ ებული გáƒáƒ ე ვიდეáƒáƒ”ბი ჩáƒáƒœáƒáƒ¬áƒ”რში áƒáƒ გáƒáƒ›áƒáƒ©áƒœáƒ“ებáƒ. მხáƒáƒšáƒáƒ“ YouTube, Vimeo, Instructure Media, Twitch დრDaily Motion URL- ებირმხáƒáƒ დáƒáƒáƒ ილი", "app.actionsBar.actionsDropdown.shareExternalVideo": "გáƒáƒ ე ვიდეáƒáƒ¡ გáƒáƒ–იáƒáƒ ებáƒ", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "შეáƒáƒ©áƒ”რეთ გáƒáƒ ე ვიდეáƒáƒ¡ გáƒáƒ–იáƒáƒ ებáƒ", "app.iOSWarning.label": "გთხáƒáƒ•თ, გáƒáƒœáƒáƒáƒ®áƒšáƒáƒ— iOS 12.2-ნდე áƒáƒœ უფრრáƒáƒ®áƒáƒš ვერსიáƒáƒ›áƒ“ე", "app.legacy.unsupportedBrowser": "რáƒáƒ’áƒáƒ ც ჩáƒáƒœáƒ¡, იყენებთ ბრáƒáƒ£áƒ–ერს, რáƒáƒ›áƒ”ლიც áƒáƒ áƒáƒ ის მხáƒáƒ დáƒáƒáƒ”რილი. სრული მხáƒáƒ დáƒáƒáƒ”რისთვის გთხáƒáƒ•თ გáƒáƒ›áƒáƒ˜áƒ§áƒ”ნáƒáƒ— áƒáƒœ {0} áƒáƒœ {1}", "app.legacy.upgradeBrowser": "რáƒáƒ’áƒáƒ ც ჩáƒáƒœáƒ¡, იყენებთ ბრáƒáƒ£áƒ–ერის ძველი ვერსიáƒáƒ¡. სრული მხáƒáƒ დáƒáƒáƒ”რისთვის გთხáƒáƒ•თ გáƒáƒœáƒáƒáƒ®áƒšáƒáƒ— თქვენი ბრáƒáƒ£áƒ–ერი", - "app.legacy.criosBrowser": "სრული მხáƒáƒ დáƒáƒáƒ ისთვის, გთხáƒáƒ•თ, IOS- ზე Safari გáƒáƒ›áƒáƒ˜áƒ§áƒ”ნáƒáƒ—" + "app.legacy.criosBrowser": "სრული მხáƒáƒ დáƒáƒáƒ ისთვის გთხáƒáƒ•თ, IOS- ზე Safari გáƒáƒ›áƒáƒ˜áƒ§áƒ”ნáƒáƒ—" } diff --git a/bigbluebutton-html5/private/locales/km.json b/bigbluebutton-html5/private/locales/km.json index 1d6b744c58592af5ab0d0b22caf6b0b20ed27447..786491ae3e99bb9e39de5cb3360c198f32fb9c58 100644 --- a/bigbluebutton-html5/private/locales/km.json +++ b/bigbluebutton-html5/private/locales/km.json @@ -50,10 +50,10 @@ "app.note.title": "កំណážáŸ‹ážáŸ’រារួមគ្នា", "app.note.label": "កំណážáŸ‹ážáŸ’ážšáž¶", "app.note.hideNoteLabel": "លាក់កំណážáŸ‹ážáŸ’ážšáž¶", + "app.note.tipLabel": "ចុច Esc ដើម្បីផ្ážáŸ„ážáž‘ៅលើរបារឧបករណáŸážŸáž˜áŸ’រាប់កម្មវិធីសរសáŸážš", "app.user.activityCheck": "ការពិនិážáŸ’យសកម្មភាពអ្នកចូលរួម", "app.user.activityCheck.label": "ពិនិážáŸ’áž™ážáž¶ážáž¾áž¢áŸ’នកចូលរួមនៅក្នុងកិច្ចប្រជុំទៀážáž‘០({0})", "app.user.activityCheck.check": "ពិនិážáŸ’áž™", - "app.note.tipLabel": "ចុច Esc ដើម្បីផ្ážáŸ„ážáž‘ៅលើរបារឧបករណáŸážŸáž˜áŸ’រាប់កម្មវិធីសរសáŸážš", "app.userList.usersTitle": "អ្នកចូលរួម", "app.userList.participantsTitle": "អ្នកចូលរួម", "app.userList.messagesTitle": "សារ", @@ -121,8 +121,6 @@ "app.meeting.meetingTimeRemaining": "áž–áŸáž›áž”្រជុំនៅសល់៖ {0}", "app.meeting.meetingTimeHasEnded": "ដល់ពáŸáž›áž€áŸ†ážŽážáŸ‹áŸ” កិច្ចប្រជុំនឹងបញ្ចប់បន្ážáž·áž…ទៀážáž“áŸáŸ‡áŸ”", "app.meeting.endedMessage": "អ្នកនឹងážáŸ’រូវបាននាំទៅកាន់ទំពáŸážšážŠáž¾áž˜", - "app.meeting.alertMeetingEndsUnderOneMinute": "កិច្ចប្រជុំនឹងបិទនៅពáŸáž›áž”ន្ážáž·áž…ទៀáž", - "app.meeting.alertBreakoutEndsUnderOneMinute": "បន្ទប់បំបែកនឹងបិទនាពáŸáž›áž”ន្ážáž·áž…ទៀáž", "app.presentation.hide": "លាក់បទបង្ហាញ", "app.presentation.notificationLabel": "បទបង្ហាញបច្ចុប្បន្ន", "app.presentation.slideContent": "ážáŸ’លឹមសារក្នុងស្លាយ", @@ -250,7 +248,6 @@ "app.leaveConfirmation.confirmLabel": "ចាកចáŸáž‰", "app.leaveConfirmation.confirmDesc": "ដក​អ្នក​ចáŸáž‰â€‹áž–ី​ការ​ប្រជុំ", "app.endMeeting.title": "បញ្ចប់កិច្ចប្រជុំ", - "app.endMeeting.description": "ážáž¾áž¢áŸ’នកពិážáž‡áž¶áž…ង់បញ្ចប់សមáŸáž™áž”្រជុំនáŸáŸ‡áž˜áŸ‚នទáŸ?", "app.endMeeting.yesLabel": "បាទ/ចាស", "app.endMeeting.noLabel": "áž‘áŸ", "app.about.title": "អំពី", @@ -271,8 +268,6 @@ "app.screenshare.screenShareLabel" : "ចែករំលែកអáŸáž€áŸ’រង់", "app.submenu.application.applicationSectionTitle": "កម្មវិធី", "app.submenu.application.animationsLabel": "ចលនា", - "app.submenu.application.audioAlertLabel": "ដំណឹងជាសម្លáŸáž„សម្រាប់ការជជែក", - "app.submenu.application.pushAlertLabel": "ដំណឹងជាផ្ទាំងលោážážŸáž˜áŸ’រាប់ការជជែក", "app.submenu.application.fontSizeControlLabel": "ទំហំ​អក្សរ​", "app.submenu.application.increaseFontBtnLabel": "បង្កើន​ទំហំ​អក្សរ​ក្នុង​កម្មវិធី", "app.submenu.application.decreaseFontBtnLabel": "បន្ážáž™â€‹áž‘ំហំ​អក្សរ​ក្នុង​កម្មវិធី", @@ -326,25 +321,25 @@ "app.actionsBar.actionsDropdown.takePresenterDesc": "ឲ្យážáŸ’លួនឯងជាអ្នកបង្ហាញážáŸ’មី", "app.actionsBar.emojiMenu.statusTriggerLabel": "ដាក់ស្ážáž¶áž“áž—áž¶áž–", "app.actionsBar.emojiMenu.awayLabel": "នៅ​ឆ្ងាយ", - "app.actionsBar.emojiMenu.awayDesc": "ប្ážáž¼ážšâ€‹ážŸáŸ’ážáž¶áž“ភាព​របស់​អ្នក​ទៅ \\\"នៅឆ្ងាយ\\\"", + "app.actionsBar.emojiMenu.awayDesc": "ប្ážáž¼ážšâ€‹ážŸáŸ’ážáž¶áž“ភាព​របស់​អ្នក​ទៅ \"នៅឆ្ងាយ\"", "app.actionsBar.emojiMenu.raiseHandLabel": "លើកដៃ", "app.actionsBar.emojiMenu.raiseHandDesc": "លើកដៃ​របស់​អ្នក​ដើម្បី​សួរ​សំនួរ", "app.actionsBar.emojiMenu.neutralLabel": "មិន​សម្រáŸáž…​ចិážáŸ’áž", - "app.actionsBar.emojiMenu.neutralDesc": "ប្ážáž¼ážšâ€‹ážŸáŸ’ážáž¶áž“ភាព​របស់​អ្នក​ទៅ \\\"មិន​សម្រáŸáž…​ចិážáŸ’áž\\\"", + "app.actionsBar.emojiMenu.neutralDesc": "ប្ážáž¼ážšâ€‹ážŸáŸ’ážáž¶áž“ភាព​របស់​អ្នក​ទៅ \"មិន​សម្រáŸáž…​ចិážáŸ’áž\"", "app.actionsBar.emojiMenu.confusedLabel": "ច្រឡំ", - "app.actionsBar.emojiMenu.confusedDesc": "ប្ážáž¼ážšâ€‹ážŸáŸ’ážáž¶áž“ភាព​របស់​អ្នក​ទៅ \\\"ច្រឡំ\\\"", + "app.actionsBar.emojiMenu.confusedDesc": "ប្ážáž¼ážšâ€‹ážŸáŸ’ážáž¶áž“ភាព​របស់​អ្នក​ទៅ \"ច្រឡំ\"", "app.actionsBar.emojiMenu.sadLabel": "មិន​សប្បាយ​ចិážáŸ’áž", - "app.actionsBar.emojiMenu.sadDesc": "ប្ážáž¼ážšâ€‹ážŸáŸ’ážáž¶áž“ភាព​របស់​អ្នក​ទៅ​ \\\"មិន​សប្បាយ​ចិážáŸ’áž\\\"", + "app.actionsBar.emojiMenu.sadDesc": "ប្ážáž¼ážšâ€‹ážŸáŸ’ážáž¶áž“ភាព​របស់​អ្នក​ទៅ​ \"មិន​សប្បាយ​ចិážáŸ’áž\"", "app.actionsBar.emojiMenu.happyLabel": "សប្បាយចិážáŸ’áž", - "app.actionsBar.emojiMenu.happyDesc": "ប្ážáž¼ážšâ€‹ážŸáŸ’ážáž¶áž“ភាព​របស់​អ្នក​ទៅ​ \\\"សប្បាយចិážáŸ’áž\\\"", + "app.actionsBar.emojiMenu.happyDesc": "ប្ážáž¼ážšâ€‹ážŸáŸ’ážáž¶áž“ភាព​របស់​អ្នក​ទៅ​ \"សប្បាយចិážáŸ’áž\"", "app.actionsBar.emojiMenu.noneLabel": "លុប​ស្ážáž¶áž“áž—áž¶áž–", "app.actionsBar.emojiMenu.noneDesc": "លុប​ស្ážáž¶áž“ភាព​របស់​អ្នក", "app.actionsBar.emojiMenu.applauseLabel": "ទះដៃ", - "app.actionsBar.emojiMenu.applauseDesc": "ប្ážáž¼ážšâ€‹ážŸáŸ’ážáž¶áž“ភាព​របស់​អ្នក​ទៅ \\\"ទះដៃ\\\"", + "app.actionsBar.emojiMenu.applauseDesc": "ប្ážáž¼ážšâ€‹ážŸáŸ’ážáž¶áž“ភាព​របស់​អ្នក​ទៅ \"ទះដៃ\"", "app.actionsBar.emojiMenu.thumbsUpLabel": "មáŸážŠáŸƒâ€‹áž¡áž¾áž„", - "app.actionsBar.emojiMenu.thumbsUpDesc": "ប្ážáž¼ážšâ€‹ážŸáŸ’ážáž¶áž“ភាព​របស់​អ្នក​ទៅ \"មáŸážŠáŸƒâ€‹áž¡áž¾áž„\"", + "app.actionsBar.emojiMenu.thumbsUpDesc": "ប្ážáž¼ážšâ€‹ážŸáŸ’ážáž¶áž“ភាព​របស់​អ្នក​ទៅ 'មáŸážŠáŸƒâ€‹áž¡áž¾áž„'", "app.actionsBar.emojiMenu.thumbsDownLabel": "មáŸâ€‹ážŠáŸƒâ€‹áž…ុះ", - "app.actionsBar.emojiMenu.thumbsDownDesc": "ប្ážáž¼ážšâ€‹ážŸáŸ’ážáž¶áž“ភាព​របស់​អ្នក​ទៅ \"មáŸâ€‹ážŠáŸƒâ€‹áž…ុះ\"", + "app.actionsBar.emojiMenu.thumbsDownDesc": "ប្ážáž¼ážšâ€‹ážŸáŸ’ážáž¶áž“ភាព​របស់​អ្នក​ទៅ 'មáŸâ€‹ážŠáŸƒâ€‹áž…ុះ'", "app.actionsBar.currentStatusDesc": "ស្ážáž¶áž“ភាព​បច្ចុប្បន្ន {0}", "app.actionsBar.captions.start": "ចាប់ផ្ážáž¾áž˜áž€áž¶ážšáž˜áž¾áž›áž…ំណងជើងភ្ជាប់", "app.actionsBar.captions.stop": "បញ្ឈប់ការមើលចំណងជើងភ្ជាប់", @@ -522,22 +517,8 @@ "app.video.videoMenuDesc": "បើកម៉ឺនុយទម្លាក់ចុះសម្រាប់វីដáŸáž¢áž¼", "app.video.chromeExtensionError": "អ្នកážáŸ’រូវážáŸ‚ដំឡើង", "app.video.chromeExtensionErrorLink": "កម្មវិធីបន្ážáŸ‚ម Chrome áž“áŸáŸ‡", - "app.video.stats.title": "ស្ážáž·ážáž·áž“ៃការážáž—្ជាប់", - "app.video.stats.packetsReceived": "កញ្ចប់បានទទួល", - "app.video.stats.packetsSent": "កញ្ចប់បានផ្ញើ", - "app.video.stats.packetsLost": "កញ្ចប់បានបាážáŸ‹áž”ង់", - "app.video.stats.bitrate": "អážáŸ’រាប៊ីáž", - "app.video.stats.lostPercentage": "ចំនួនភាគរយសរុបដែលបានបាážáŸ‹áž”ង់", - "app.video.stats.lostRecentPercentage": "ភាគរយážáŸ’មីៗដែលបានបាážáŸ‹áž”ង់", - "app.video.stats.dimensions": "វិមាážáŸ’ážš", - "app.video.stats.codec": "កូដឌិក", - "app.video.stats.decodeDelay": "áž–áŸáž›áž–ន្យាសម្រាប់ការបម្លែង", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "ការប្រើប្រាស់កូដ", - "app.video.stats.currentDelay": "ពន្យាពáŸáž›áž”ច្ចុប្បន្ន", "app.fullscreenButton.label": "ធ្វើឱ្យ {0} អáŸáž€áŸ’រង់ពáŸáž‰", "app.meeting.endNotification.ok.label": "យល់ព្រម", - "app.whiteboard.annotations.poll": "លទ្ធផលការស្ទង់មážáž·ážáŸ’រូវបានចáŸáž‰áž•្សាយ", "app.whiteboard.toolbar.tools": "ឧបករណáŸ", "app.whiteboard.toolbar.tools.hand": "អូស", "app.whiteboard.toolbar.tools.pencil": "ážáŸ’មៅដៃ", diff --git a/bigbluebutton-html5/private/locales/kn.json b/bigbluebutton-html5/private/locales/kn.json index 9bb3d2d453a03f88bd60f70c34fabac8a15a6553..ca167a98f1039e266866ee87e4bce4536fb9d923 100644 --- a/bigbluebutton-html5/private/locales/kn.json +++ b/bigbluebutton-html5/private/locales/kn.json @@ -50,10 +50,10 @@ "app.note.title": "ಹಂಚಿದ ಟಿಪà³à²ªà²£à²¿à²—ಳà³", "app.note.label": "ಸೂಚನೆ", "app.note.hideNoteLabel": "ಟಿಪà³à²ªà²£à²¿ ಮರೆಮಾಡಿ", + "app.note.tipLabel": "ಫೋಕಸೠಎಡಿಟರೠಟೂಲà³â€Œà²¬à²¾à²°à³ ಮಾಡಲೠEsc ಒತà³à²¤à²¿à²°à²¿", "app.user.activityCheck": "ಬಳಕೆದಾರರ ಚಟà³à²µà²Ÿà²¿à²•ೆ ಪರಿಶೀಲನೆ", "app.user.activityCheck.label": "ಬಳಕೆದಾರರೠಇನà³à²¨à³‚ ಸà²à³†à²¯à²²à³à²²à²¿à²¦à³à²¦à²¾à²°à³†à²¯à³‡ ಎಂದೠಪರಿಶೀಲಿಸಿ ({0})", "app.user.activityCheck.check": "ಪರಿಶೀಲಿಸಿ", - "app.note.tipLabel": "ಫೋಕಸೠಎಡಿಟರೠಟೂಲà³â€Œà²¬à²¾à²°à³ ಮಾಡಲೠEsc ಒತà³à²¤à²¿à²°à²¿", "app.userList.usersTitle": "ಬಳಕೆದಾರರà³", "app.userList.participantsTitle": "à²à²¾à²—ವಹಿಸà³à²µà²µà²°à³", "app.userList.messagesTitle": "ಸಂದೇಶಗಳà³", @@ -122,8 +122,6 @@ "app.meeting.meetingTimeRemaining": "ಸà²à³†à²¯ ಸಮಯ ಉಳಿದಿದೆ: {0}", "app.meeting.meetingTimeHasEnded": "ಸಮಯ ಕೊನೆಗೊಂಡಿತà³. ಸà²à³† ಶೀಘà³à²°à²¦à²²à³à²²à³‡ ಮà³à²šà³à²šà²²à²¿à²¦à³†", "app.meeting.endedMessage": "ನಿಮà³à²®à²¨à³à²¨à³ ಮತà³à²¤à³† ಮà³à²–ಪà³à²Ÿà²•à³à²•ೆ ರವಾನಿಸಲಾಗà³à²¤à³à²¤à²¦à³†", - "app.meeting.alertMeetingEndsUnderOneMinute": "ಸà²à³† ಒಂದೠನಿಮಿಷದಲà³à²²à²¿ ಮà³à²šà³à²šà³à²¤à³à²¤à²¿à²¦à³†.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "ಬà³à²°à³‡à²•à³ out ಟೠಒಂದೠನಿಮಿಷದಲà³à²²à²¿ ಮà³à²šà³à²šà³à²¤à³à²¤à²¿à²¦à³†.", "app.presentation.hide": "ಪà³à²°à²¸à³à²¤à³à²¤à²¿à²¯à²¨à³à²¨à³ ಮರೆಮಾಡಿ", "app.presentation.notificationLabel": "ಪà³à²°à²¸à³à²¤à³à²¤ ಪà³à²°à²¸à³à²¤à³à²¤à²¿", "app.presentation.slideContent": "ಜಾರà³à²• ವಿಷಯ", @@ -263,7 +261,6 @@ "app.leaveConfirmation.confirmLabel": "ಹೊರಬನà³à²¨à²¿", "app.leaveConfirmation.confirmDesc": "ನಿಮà³à²®à²¨à³à²¨à³ ಸà²à³†à²¯à²¿à²‚ದ ಹೊರಹಾಕà³à²¤à³à²¤à²¦à³†", "app.endMeeting.title": "ಸà²à³†à²¯à²¨à³à²¨à³ ಕೊನೆಗೊಳಿಸಿ", - "app.endMeeting.description": "ಈ ಅಧಿವೇಶನವನà³à²¨à³ ಕೊನೆಗೊಳಿಸಲೠನೀವೠಖಚಿತವಾಗಿ ಬಯಸà³à²µà²¿à²°à²¾?", "app.endMeeting.yesLabel": "ಹೌದà³", "app.endMeeting.noLabel": "ಇಲà³à²²", "app.about.title": "ಬಗà³à²—ೆ", @@ -284,10 +281,6 @@ "app.screenshare.screenShareLabel" : "ಪರದೆಯ ಪಾಲà³", "app.submenu.application.applicationSectionTitle": "ಅಪà³à²²à²¿à²•ೇಶನà³", "app.submenu.application.animationsLabel": "ಅನಿಮೇಷನà³", - "app.submenu.application.audioAlertLabel": "ಚಾಟà³â€Œà²—ಾಗಿ ಆಡಿಯೊ ಎಚà³à²šà²°à²¿à²•ೆಗಳà³", - "app.submenu.application.pushAlertLabel": "ಚಾಟà³â€Œà²—ಾಗಿ ಪà³à²Ÿà²¿à²•ೆ ಎಚà³à²šà²°à²¿à²•ೆಗಳà³", - "app.submenu.application.userJoinAudioAlertLabel": "ಬಳಕೆದಾರರ ಸೇರà³à²ªà²¡à³†à²—ಾಗಿ ಆಡಿಯೊ ಎಚà³à²šà²°à²¿à²•ೆಗಳà³", - "app.submenu.application.userJoinPushAlertLabel": "ಬಳಕೆದಾರ ಸೇರà³à²ªà²¡à³†à²—ಾಗಿ ಪà³à²Ÿà²¿à²µ ಎಚà³à²šà²°à²¿à²•ೆಗಳà³", "app.submenu.application.fontSizeControlLabel": "ಅಕà³à²·à²° ಗಾತà³à²°", "app.submenu.application.increaseFontBtnLabel": "ಅಪà³à²²à²¿à²•ೇಶನೠಅಕà³à²·à²° ವಿನà³à²¯à²¾à²¸ ಗಾತà³à²°à²µà²¨à³à²¨à³ ಹೆಚà³à²šà²¿à²¸à²¿", "app.submenu.application.decreaseFontBtnLabel": "ಅಪà³à²²à²¿à²•ೇಶನೠವಿನà³à²¯à²¾à²¸ ಗಾತà³à²°à²µà²¨à³à²¨à³ ಕಡಿಮೆ ಮಾಡಿ", @@ -569,19 +562,6 @@ "app.video.videoMenuDesc": "ವೀಡಿಯೊ ಮೆನೠಡà³à²°à²¾à²ªà³â€Œà²¡à³Œà²¨à³ ತೆರೆಯಿರಿ", "app.video.chromeExtensionError": "ನೀವೠಸà³à²¥à²¾à²ªà²¿à²¸à²¬à³‡à²•à³", "app.video.chromeExtensionErrorLink": "ಈ ಕà³à²°à³‹à²®à³â€Œ ವಿಸà³à²¤à²°à²£à³†", - "app.video.stats.title": "ಸಂಪರà³à²• ಅಂಕಿಅಂಶಗಳà³", - "app.video.stats.packetsReceived": "ಪà³à²¯à²¾à²•ೆಟà³â€Œà²—ಳನà³à²¨à³ ಸà³à²µà³€à²•ರಿಸಲಾಗಿದೆ", - "app.video.stats.packetsSent": "ಪà³à²¯à²¾à²•ೆಟà³â€Œà²—ಳನà³à²¨à³ ಕಳà³à²¹à²¿à²¸à²²à²¾à²—ಿದೆ", - "app.video.stats.packetsLost": "ಪà³à²¯à²¾à²•ೆಟà³â€Œà²—ಳೠಕಳೆದà³à²¹à³‹à²—ಿವೆ", - "app.video.stats.bitrate": "ಬಿಟà³â€Œ ರೇಟà³â€Œ ", - "app.video.stats.lostPercentage": "ಒಟà³à²Ÿà³ ಶೇಕಡಾವಾರೠಕಳೆದà³à²¹à³‹à²—ಿದೆ", - "app.video.stats.lostRecentPercentage": "ಇತà³à²¤à³€à²šà²¿à²¨ ಶೇಕಡಾವಾರೠನಷà³à²Ÿà²µà²¾à²—ಿದೆ", - "app.video.stats.dimensions": "ಆಯಾಮಗಳà³", - "app.video.stats.codec": "ಕೋಡೆಕà³", - "app.video.stats.decodeDelay": "ಡಿಕೋಡೠವಿಳಂಬ", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "ಎನà³à²•ೋಡೠಬಳಕೆ", - "app.video.stats.currentDelay": "ಪà³à²°à²¸à³à²¤à³à²¤ ವಿಳಂಬ", "app.fullscreenButton.label": "{0} ಪೂರà³à²£à²ªà²°à²¦à³† ಮಾಡಿ", "app.deskshare.iceConnectionStateError": "ಪರದೆಯನà³à²¨à³ ಹಂಚಿಕೊಳà³à²³à³à²µà²¾à²— ಸಂಪರà³à²• ವಿಫಲವಾಗಿದೆ (ICE ದೋಷ 1108)", "app.sfu.mediaServerConnectionError2000": "ಮಾಧà³à²¯à²® ಸರà³à²µà²°à³â€Œà²—ೆ ಸಂಪರà³à²•ಿಸಲೠಸಾಧà³à²¯à²µà²¿à²²à³à²² (ದೋಷ 2000)", @@ -594,7 +574,6 @@ "app.sfu.invalidSdp2202":"ಕà³à²²à³ˆà²‚ಟೠಅಮಾನà³à²¯ ಮಾಧà³à²¯à²® ವಿನಂತಿಯನà³à²¨à³ ರಚಿಸಿದೆ (ಎಸà³â€Œà²¡à²¿à²ªà²¿ ದೋಷ 2202)", "app.sfu.noAvailableCodec2203": "ಸರà³à²µà²°à³â€Œà²—ೆ ಸೂಕà³à²¤à²µà²¾à²¦ ಕೊಡೆಕೠಸಿಗಲಿಲà³à²² (ದೋಷ 2203)", "app.meeting.endNotification.ok.label": "ಸರಿ", - "app.whiteboard.annotations.poll": "ಸಮೀಕà³à²·à³†à²¯ ಫಲಿತಾಂಶಗಳನà³à²¨à³ ಪà³à²°à²•ಟಿಸಲಾಗಿದೆ", "app.whiteboard.toolbar.tools": "ಪರಿಕರಗಳà³", "app.whiteboard.toolbar.tools.hand": "ಪà³à²¯à²¾à²¨à³", "app.whiteboard.toolbar.tools.pencil": "ಪೆನà³à²¸à²¿à²²à³/ಸೀಸದ ಕಡà³à²¡à²¿", @@ -675,7 +654,6 @@ "app.externalVideo.autoPlayWarning": "ಮಾಧà³à²¯à²® ಸಿಂಕà³à²°à³Šà²¨à³ˆà²¸à³‡à²¶à²¨à³ ಅನà³à²¨à³ ಸಕà³à²°à²¿à²¯à²—ೊಳಿಸಲೠವೀಡಿಯೊವನà³à²¨à³ ಪà³à²²à³‡ ಮಾಡಿ", "app.network.connection.effective.slow": "ಸಂಪರà³à²• ಸಮಸà³à²¯à³†à²—ಳನà³à²¨à³ ನಾವೠಗಮನಿಸà³à²¤à³à²¤à²¿à²¦à³à²¦à³‡à²µà³†.", "app.network.connection.effective.slow.help": "ಹೆಚà³à²šà²¿à²¨ ಮಾಹಿತಿ", - "app.externalVideo.noteLabel": "ಗಮನಿಸಿ: ಹಂಚಿದ ಬಾಹà³à²¯ ವೀಡಿಯೊಗಳೠರೆಕಾರà³à²¡à²¿à²‚ಗà³â€Œà²¨à²²à³à²²à²¿ ಗೋಚರಿಸà³à²µà³à²¦à²¿à²²à³à²². ಯೂಟà³à²¯à³‚ಬà³, ವಿಮಿಯೋ, ಇನà³â€Œà²¸à³à²Ÿà³à²°à²•à³à²šà²°à³ ಮೀಡಿಯಾ, ಟà³à²µà²¿à²šà³ ಮತà³à²¤à³ ಡೈಲಿ ಮೋಷನೠURL ಗಳನà³à²¨à³ ಬೆಂಬಲಿಸಲಾಗà³à²¤à³à²¤à²¦à³†.", "app.actionsBar.actionsDropdown.shareExternalVideo": "ಬಾಹà³à²¯ ವೀಡಿಯೊವನà³à²¨à³ ಹಂಚಿಕೊಳà³à²³à²¿", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "ಬಾಹà³à²¯ ವೀಡಿಯೊ ಹಂಚಿಕೊಳà³à²³à³à²µà³à²¦à²¨à³à²¨à³ ನಿಲà³à²²à²¿à²¸à²¿", "app.iOSWarning.label": "ದಯವಿಟà³à²Ÿà³ iOS 12.2 ಅಥವಾ ಹೆಚà³à²šà²¿à²¨à²¦à²•à³à²•ೆ ಅಪà³â€Œà²—à³à²°à³‡à²¡à³ ಮಾಡಿ", diff --git a/bigbluebutton-html5/private/locales/ko_KR.json b/bigbluebutton-html5/private/locales/ko_KR.json index 466902f36c3d9fef387fc51b0ab02c6ceedd812a..c7e030fb848c9f5f8b6ac0efad96ee3ca397ed0a 100644 --- a/bigbluebutton-html5/private/locales/ko_KR.json +++ b/bigbluebutton-html5/private/locales/ko_KR.json @@ -18,6 +18,7 @@ "app.chat.dropdown.save": "ì €ìž¥", "app.chat.label": "채팅", "app.chat.offline": "오프ë¼ì¸", + "app.chat.pollResult": "설문 ê²°ê³¼", "app.chat.emptyLogLabel": "채팅 ê¸°ë¡ ì§€ìš°ê¸°", "app.chat.clearPublicChatMessage": "공개 채팅 기ë¡ì´ 주관ìžì— ì˜í•´ 지워졌습니다 ", "app.chat.multi.typing": "여러 사용ìžê°€ 타ì´í•‘í•˜ê³ ìžˆìŠµë‹ˆë‹¤ ", @@ -50,10 +51,10 @@ "app.note.title": "노트 ê³µìœ ", "app.note.label": "노트", "app.note.hideNoteLabel": "노트 숨기기", + "app.note.tipLabel": "ì—디터 íˆ´ë°”ì— í¬ì»¤ìŠ¤ë¥¼ 위해 ESC 를 누르세요", "app.user.activityCheck": "ì‚¬ìš©ìž í™œë™ ì²´í¬", "app.user.activityCheck.label": "사용ìžê°€ ({0})ë¯¸íŒ…ì— ì•„ì§ ìžˆëŠ”ì§€ ì²´í¬", "app.user.activityCheck.check": "ì²´í¬", - "app.note.tipLabel": "ì—디터 íˆ´ë°”ì— í¬ì»¤ìŠ¤ë¥¼ 위해 ESC 를 누르세요", "app.userList.usersTitle": "사용ìž", "app.userList.participantsTitle": "참가ìž", "app.userList.messagesTitle": "메시지", @@ -63,6 +64,7 @@ "app.userList.presenter": "발표ìž", "app.userList.you": "ë‹¹ì‹ ", "app.userList.locked": "ìž ê¹€", + "app.userList.byModerator": "(중재ìž)ì— ì˜í•´ ", "app.userList.label": "ì‚¬ìš©ìž ë¦¬ìŠ¤íŠ¸", "app.userList.toggleCompactView.label": "간단한 보기로 ì „í™˜", "app.userList.guest": "ì†ë‹˜", @@ -72,6 +74,8 @@ "app.userList.menu.chat.label": "비공개 채팅 시작", "app.userList.menu.clearStatus.label": "ìƒíƒœ 지우기", "app.userList.menu.removeUser.label": "ì‚¬ìš©ìž ì«“ì•„ë‚´ê¸°", + "app.userList.menu.removeConfirmation.label": "ì‚¬ìš©ìž ì‚ì œ ({0})", + "app.userlist.menu.removeConfirmation.desc": "ì´ ì‚¬ìš©ìžê°€ ì„¸ì…˜ì— ìž¬ì§„ìž…ì„ ë°©ì§€", "app.userList.menu.muteUserAudio.label": "ì‚¬ìš©ìž ìŒì†Œê±°", "app.userList.menu.unmuteUserAudio.label": "ì‚¬ìš©ìž ìŒì†Œê±° 취소", "app.userList.userAriaLabel": "{0}{1}{2} ìƒíƒœ {3}", @@ -92,6 +96,8 @@ "app.userList.userOptions.unmuteAllDesc": "미팅 ìŒì†Œê±° í•´ì œ", "app.userList.userOptions.lockViewersLabel": "ëª¨ë“ ê´€ëžŒìž ìž ê·¸ê¸°", "app.userList.userOptions.lockViewersDesc": "ë¯¸íŒ…ì˜ ì°¸ì„ìž ê¸°ëŠ¥ë“¤ì„ ìž ê·¸ê¸°", + "app.userList.userOptions.connectionStatusLabel": "ì ‘ì† ìƒíƒœ", + "app.userList.userOptions.connectionStatusDesc": "ì‚¬ìš©ìž ì ‘ì†ìƒíƒœ 보기", "app.userList.userOptions.disableCam": "관람ìžë“¤ì˜ ì›¹ìº ì‚¬ìš© 중지", "app.userList.userOptions.disableMic": "관람ìžë“¤ì˜ 마ì´í¬ 사용 중지", "app.userList.userOptions.disablePrivChat": "비공개 채팅 사용 중지", @@ -112,6 +118,7 @@ "app.media.screenshare.start": "스í¬ë¦°ê³µìœ 시작", "app.media.screenshare.end": "스í¬ë¦°ê³µìœ 종료 ", "app.media.screenshare.unavailable": "스í¬ë¦°ê³µìœ 불가", + "app.media.screenshare.notSupported": "í™”ë©´ê³µìœ ê°€ ì´ ë¸Œë¼ìš°ì €ì—서는 ì§€ì›ë˜ì§€ 않습니다 ", "app.media.screenshare.autoplayBlockedDesc": "발표ìžì˜ 스í¬ë¦°ì„ 보여주기 위해 ë‹¹ì‹ ì˜ í—ˆê°€ê°€ 필요합니다 ", "app.media.screenshare.autoplayAllowLabel": "ê³µìœ ìŠ¤í¬ë¦° 보기", "app.screenshare.notAllowed": "ì—러: 스í¬ë¦°ì— ì ‘ê·¼í•˜ê¸° 위한 ê¶Œí•œì´ ë¶€ì—¬ë˜ì§€ 않았습니다.", @@ -122,10 +129,13 @@ "app.meeting.meetingTimeRemaining": "ë¯¸íŒ…ì‹œê°„ì€ {0} 남았습니다 ", "app.meeting.meetingTimeHasEnded": "시간종료. ë¯¸íŒ…ì€ ì¡°ë§Œê°„ 종료 ë©ë‹ˆë‹¤ ", "app.meeting.endedMessage": "홈화면으로 ëŒì•„갑니다 ", - "app.meeting.alertMeetingEndsUnderOneMinute": "ë¯¸íŒ…ì€ 1ë¶„ í›„ì— ì¢…ë£Œë©ë‹ˆë‹¤. ", - "app.meeting.alertBreakoutEndsUnderOneMinute": "ë¸Œë ˆì´í¬ì•„ì›ƒì´ 1ë¶„ í›„ì— ì¢…ë£Œë©ë‹ˆë‹¤ ", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "ë¯¸íŒ…ì´ 1ë¶„ë‚´ì— ë§ˆê°ë©ë‹ˆë‹¤ ", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "ë¯¸íŒ…ì´ {0} ë¶„ë‚´ì— ë§ˆê°ë©ë‹ˆë‹¤ ", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "ë¸Œë ˆì´í¬ì•„ì›ƒì´ {0} ë¶„ë‚´ì— ë§ˆê°ë©ë‹ˆë‹¤ ", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "ë¸Œë ˆì´í¬ì•„ì›ƒì´ 1 ë¶„ë‚´ì— ë§ˆê°ë©ë‹ˆë‹¤ ", "app.presentation.hide": "프리ì í…Œì´ì…˜ 숨기기", "app.presentation.notificationLabel": "현재 프리ì í…Œì´ì…˜", + "app.presentation.downloadLabel": "다운로드", "app.presentation.slideContent": "슬ë¼ì´ë“œ 컨í…ì¸ ", "app.presentation.startSlideContent": "슬ë¼ì´ë“œ 컨í…ì¸ ì‹œìž‘", "app.presentation.endSlideContent": "슬ë¼ì´ë“œ 컨í…ì¸ ì¢…ë£Œ", @@ -170,6 +180,7 @@ "app.presentationUploder.rejectedError": "ì„ íƒí•œ 파ì¼(들)ì´ ê±°ì ˆ ë˜ì—ˆìŠµë‹ˆë‹¤. íŒŒì¼ ì¢…ë¥˜(들)를 살펴 보세요 ", "app.presentationUploder.upload.progress": "업로드중 ({0}%)", "app.presentationUploder.upload.413": "파ì¼ì´ 너무 í½ë‹ˆë‹¤. 여러파ì¼ë¡œ 나누세요 ", + "app.presentationUploder.genericError": "어머나. ë”ê°€ 잘못 ë˜ì—ˆì–´ìš” ", "app.presentationUploder.upload.408": "ìš”ì²í•˜ì‹ 업로드 í† í°ì´ 만료ë˜ì—ˆìŠµë‹ˆë‹¤.", "app.presentationUploder.upload.404": "404: ìž˜ëª»ëœ ì—…ë¡œë“œ í† í°", "app.presentationUploder.upload.401": "프리ì í…Œì´ì…˜ 업로드 í† í° ìš”ì²ì´ 실패했습니다.", @@ -191,6 +202,13 @@ "app.presentationUploder.tableHeading.filename": "íŒŒì¼ ì´ë¦„", "app.presentationUploder.tableHeading.options": "옵션", "app.presentationUploder.tableHeading.status": "ìƒíƒœ", + "app.presentationUploder.uploading": "업로드 {0} {1}", + "app.presentationUploder.uploadStatus": "{0} 중 {1} 업로드 완료 ", + "app.presentationUploder.completed": "{0} 업로드 완료 ", + "app.presentationUploder.item" : "ì•„ì´í…œ", + "app.presentationUploder.itemPlural" : "ì•„ì´í…œë“¤", + "app.presentationUploder.clearErrors": "ì—러 ì œê±° ", + "app.presentationUploder.clearErrorsDesc": "프리ì í…Œì´ì…˜ 업로드 실패 ì œê±° ", "app.poll.pollPaneTitle": "설문조사", "app.poll.quickPollTitle": "ë¹ ë¥¸ì„¤ë¬¸", "app.poll.hidePollDesc": "설문메뉴 숨기기", @@ -236,6 +254,7 @@ "app.connectingMessage": "ì ‘ì†ì¤‘ ...", "app.waitingMessage": "ì ‘ì†ëŠê¹€. ìž¬ì ‘ì† ì‹œë„ {0} ì´ˆ ...", "app.retryNow": "다시 ì‹œë„ í•´ 보세요 ", + "app.muteWarning.label": "ìžì‹ ì„ ìŒì†Œê±° 하기 위해 {0} ì„ í´ë¦", "app.navBar.settingsDropdown.optionsLabel": "옵션", "app.navBar.settingsDropdown.fullscreenLabel": "í°í™”면으로 ", "app.navBar.settingsDropdown.settingsLabel": "ì„¤ì •", @@ -263,7 +282,7 @@ "app.leaveConfirmation.confirmLabel": "ë– ë‚˜ê¸°", "app.leaveConfirmation.confirmDesc": "미팅ì—서 로그 아웃", "app.endMeeting.title": "미팅 ë", - "app.endMeeting.description": "ì´ ì„¸ì…˜ì„ ì¢…ë£Œ í•˜ì‹œê² ìŠµë‹ˆê¹Œ ?", + "app.endMeeting.description": "ëª¨ë“ ì´ë“¤ê³¼ 함께 ì´ ë¯¸íŒ…ì„ ëë‚´ì‹œê² ìŠµë‹ˆê¹Œ ? ( ëª¨ë“ ì‚¬ìš©ìžëŠ” ì ‘ì†í•´ì œ ë©ë‹ˆë‹¤ )", "app.endMeeting.yesLabel": "예", "app.endMeeting.noLabel": "아니요", "app.about.title": "개요", @@ -284,10 +303,6 @@ "app.screenshare.screenShareLabel" : "화면 ê³µìœ ", "app.submenu.application.applicationSectionTitle": "ì‹ ì²", "app.submenu.application.animationsLabel": "ì• ë‹ˆë©”ì´ì…˜", - "app.submenu.application.audioAlertLabel": "채팅 ìŒì„± 알림", - "app.submenu.application.pushAlertLabel": "채팅 íŒì—… 알림", - "app.submenu.application.userJoinAudioAlertLabel": "ì‚¬ìš©ìž ìž…ìž¥ 시 ìŒì„± 알림", - "app.submenu.application.userJoinPushAlertLabel": "ì‚¬ìš©ìž ìž…ìž¥ 시 íŒì—… 알림", "app.submenu.application.fontSizeControlLabel": "ê¸€ìž í¬ê¸°", "app.submenu.application.increaseFontBtnLabel": "ì‘용프로그램 글ìží¬ê¸° 확대", "app.submenu.application.decreaseFontBtnLabel": "ì‘용프로그램 ê¸€ìž í¬ê¸° 줄임", @@ -295,6 +310,12 @@ "app.submenu.application.languageLabel": "ì‘용프로그램 언어", "app.submenu.application.languageOptionLabel": "언어 ì„ íƒ", "app.submenu.application.noLocaleOptionLabel": "활성 ë¡œì¼€ì¼ ì—†ìŒ", + "app.submenu.notification.SectionTitle": "공지사í•", + "app.submenu.notification.Desc": "ë‹¹ì‹ ì´ ë¬´ì—‡ì„ ì–´ë–»ê²Œ 공지 í• ê²ƒì¸ì§€ë¥¼ ì •ì˜ ", + "app.submenu.notification.audioAlertLabel": "오디오 ê²½ê³ ", + "app.submenu.notification.pushAlertLabel": "íŒì—… ê²½ê³ ", + "app.submenu.notification.messagesLabel": "채팅 메시지", + "app.submenu.notification.userJoinLabel": "ì‚¬ìš©ìž í•©ë¥˜", "app.submenu.audio.micSourceLabel": "마ì´í¬ 소스 ", "app.submenu.audio.speakerSourceLabel": "스피커 소스 ", "app.submenu.audio.streamVolumeLabel": "ë‹¹ì‹ ì˜ ì†Œë¦¬ 볼륨", @@ -318,6 +339,10 @@ "app.settings.dataSavingTab.screenShare": "ë°ìФí¬íƒ‘ ê³µìœ ì‚¬ìš© 가능 ", "app.settings.dataSavingTab.description": "ë„¤íŠ¸ì›Œí¬ ì ˆì•½ì„ ìœ„í•´ 현재 ë³´ì´ëŠ”ê²ƒì„ ì¡°ì •", "app.settings.save-notification.label": "ì„¤ì •ì´ ì €ìž¥ë˜ì—ˆìŠµë‹ˆë‹¤ ", + "app.statusNotifier.lowerHands": "ì† ë‚´ë¦¬ê¸°", + "app.statusNotifier.raisedHandsTitle": "ì† ë“¤ê¸°", + "app.statusNotifier.raisedHandDesc": "{0} ëª…ì´ ì†ì„ 들었ìŒ", + "app.statusNotifier.and": "ê·¸ë¦¬ê³ ", "app.switch.onLabel": "켜기 ", "app.switch.offLabel": "ë„기", "app.talkingIndicator.ariaMuteDesc" : "사용ìžë¥¼ ìŒì†Œê±°í•˜ë ¤ë©´ ì„ íƒ", @@ -482,6 +507,8 @@ "app.userList.guest.pendingGuestUsers": "{0} 명 ë³´ë¥˜ëœ ì†ë‹˜ ", "app.userList.guest.pendingGuestAlert": "ì„¸ì…˜ì— í•©ë¥˜í›„ ë‹¹ì‹ ì˜ ìŠ¹ì¸ì„ 기다림", "app.userList.guest.rememberChoice": "ì„ íƒì„ 기억", + "app.userList.guest.acceptLabel": "수용", + "app.userList.guest.denyLabel": "ê±°ì ˆ", "app.user-info.title": "ë””ë ‰í† ë¦¬ 검색", "app.toast.breakoutRoomEnded": "ë¸Œë ˆì´í¬ì•„웃 ë£¸ì´ ì¢…ë£Œ ë˜ì—ˆìŠµë‹ˆë‹¤. 오디오로 다시 합류하세요", "app.toast.chat.public": "새로운 공개 채팅 메시지", @@ -496,6 +523,7 @@ "app.notification.recordingPaused": "ì´ ì„¸ì…˜ì€ ë”ì´ìƒ 녹화ë˜ì§€ 않습니다 ", "app.notification.recordingAriaLabel": "녹화 시간", "app.notification.userJoinPushAlert": "{0} ê°€ ì„¸ì…˜ì— í•©ë¥˜í–ˆìŠµë‹ˆë‹¤", + "app.submenu.notification.raiseHandLabel": "ì†ë“¤ê¸°", "app.shortcut-help.title": "키보드 바로가기", "app.shortcut-help.accessKeyNotAvailable": "ì ‘ì† í‚¤ 불가 ", "app.shortcut-help.comboLabel": "Combo", @@ -529,6 +557,12 @@ "app.lock-viewers.button.cancel": "취소", "app.lock-viewers.locked": "ìž ê¹€", "app.lock-viewers.unlocked": "í•´ì œ", + "app.connection-status.ariaTitle": "modal ì˜ ì ‘ì† ìƒíƒœ", + "app.connection-status.title": "ì ‘ì† ìƒíƒœ", + "app.connection-status.description": "ì‚¬ìš©ìž ì ‘ì†ìƒíƒœ 보기", + "app.connection-status.empty": "ì ‘ì†ê´€ë ¨í•´ì„œ 아무런 ë¬¸ì œê°€ 없습니다 ", + "app.connection-status.more": "좀 ë”", + "app.connection-status.offline": "오프ë¼ì¸", "app.recording.startTitle": "녹화 시작", "app.recording.stopTitle": "녹화 ì¼ì‹œì¤‘ì§€", "app.recording.resumeTitle": "녹화 재시작", @@ -536,10 +570,17 @@ "app.recording.stopDescription": "녹화를 ì¼ì‹œì¤‘ì§€ í•˜ì‹œê² ìŠµë‹ˆê¹Œ ? 녹화 재시작 ë²„íŠ¼ì„ ëˆ„ë¥´ë©´ ê³„ì† ë…¹í™”í• ìˆ˜ 있습니다 ", "app.videoPreview.cameraLabel": "ì¹´ë©”ë¼", "app.videoPreview.profileLabel": "품질", + "app.videoPreview.quality.low": "ë‚®ìŒ", + "app.videoPreview.quality.medium": "중간", + "app.videoPreview.quality.high": "높ìŒ", + "app.videoPreview.quality.hd": "ê³ í•´ìƒë„", "app.videoPreview.cancelLabel": "취소", "app.videoPreview.closeLabel": "닫기", "app.videoPreview.findingWebcamsLabel": "ì›¹ìº ì°¾ê¸°", "app.videoPreview.startSharingLabel": "ê³µìœ ì‹œìž‘", + "app.videoPreview.stopSharingLabel": "ê³µìœ ì¤‘ì§€", + "app.videoPreview.stopSharingAllLabel": "ëª¨ë‘ ì¤‘ì§€", + "app.videoPreview.sharedCameraLabel": "ì´ ì¹´ë©”ë¼ëŠ” ì´ë¯¸ ê³µìœ ë˜ê³ 있습니다 ", "app.videoPreview.webcamOptionLabel": "ì›¹ìº ì„ íƒ", "app.videoPreview.webcamPreviewLabel": "ì›¹ìº ë¯¸ë¦¬ë³´ê¸°", "app.videoPreview.webcamSettingsTitle": "ì›¹ìº ì„¤ì •", @@ -569,19 +610,8 @@ "app.video.videoMenuDesc": "비디오 메뉴를 드ë¡ë‹¤ìš´ 메뉴로 열기 ", "app.video.chromeExtensionError": "설치 하셔야 합니다 ", "app.video.chromeExtensionErrorLink": "ì´ í¬ë¡¬ 확장프로그램", - "app.video.stats.title": "ì ‘ì† í˜„í™©", - "app.video.stats.packetsReceived": "패킷 ì ‘ìˆ˜ ", - "app.video.stats.packetsSent": "패킷 ì „ì†¡", - "app.video.stats.packetsLost": "패킷 ì†ì‹¤", - "app.video.stats.bitrate": "ë¹„íŠ¸ë ˆì´íЏ", - "app.video.stats.lostPercentage": "ì´ ì‹¤íŒ¨ìœ¨", - "app.video.stats.lostRecentPercentage": "최근 실패율", - "app.video.stats.dimensions": "치수", - "app.video.stats.codec": "ì½”ë±", - "app.video.stats.decodeDelay": "디코딩 지연", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "ì¸ì½”딩 사용율", - "app.video.stats.currentDelay": "현재 지연", + "app.video.pagination.prevPage": "ì´ì „ 비디오 보기", + "app.video.pagination.nextPage": "ë‹¤ìŒ ë¹„ë””ì˜¤ 보기", "app.fullscreenButton.label": "{0} ì„ ê½‰ì°¬í™”ë©´ìœ¼ë¡œ ", "app.deskshare.iceConnectionStateError": "스í¬ë¦° ê³µìœ ì¤‘ ì—°ê²° 실패(ICE 오류 1108)", "app.sfu.mediaServerConnectionError2000": "미디어 ì„œë²„ì— ì—°ê²°í• ìˆ˜ ì—†ìŒ(오류 2000)", @@ -594,7 +624,8 @@ "app.sfu.invalidSdp2202":"í´ë¼ì´ì–¸íŠ¸ê°€ ìœ íš¨í•˜ì§€ ì•Šì€ ë¯¸ë””ì–´ ìš”ì²ì„ ìƒì„±í–ˆìŠµë‹ˆë‹¤(SDP 오류 2202)", "app.sfu.noAvailableCodec2203": "서버ì—서 ì ì ˆí•œ ì½”ë±ì„ ì°¾ì„ ìˆ˜ 없습니다(오류 2203)", "app.meeting.endNotification.ok.label": "예", - "app.whiteboard.annotations.poll": "설문결과 공개 ", + "app.whiteboard.annotations.poll": "설문조사결과가 발표ë˜ì—ˆìŠµë‹ˆë‹¤ ", + "app.whiteboard.annotations.pollResult": "설문 ê²°ê³¼", "app.whiteboard.toolbar.tools": "ë„구들", "app.whiteboard.toolbar.tools.hand": "Pan", "app.whiteboard.toolbar.tools.pencil": "ì—°í•„", @@ -675,7 +706,7 @@ "app.externalVideo.autoPlayWarning": "미디어 ë™ê¸°í™”를 가능하게 하기 위해 비디오 재ìƒ", "app.network.connection.effective.slow": "ì ‘ì†ê°€ëŠ¥ì„± ë¬¸ì œë¥¼ ì•Œë ¤ë“œë¦½ë‹ˆë‹¤ ", "app.network.connection.effective.slow.help": "ë” ë§Žì€ ì •ë³´ ", - "app.externalVideo.noteLabel": "알림 : 외부 비디오 ê³µìœ ëŠ” ë…¹í™”ì— ë‚˜íƒ€ë‚˜ì§€ 않습니다. Youtube, Vimeo, Instructure Media, Twitch and Daily Motion URL ì€ ì§€ì›ë©ë‹ˆë‹¤ ", + "app.externalVideo.noteLabel": "ì£¼ì˜ : 외부비디오 ê³µìœ ëŠ” ë…¹í™”ì¤‘ì— ë³´ì´ì§€ 않습니다. ìœ íŠœë¸Œ, 비메오, 트위치 등과 ë™ì˜ìƒ URL ì€ ì§€ì› ë©ë‹ˆë‹¤ ", "app.actionsBar.actionsDropdown.shareExternalVideo": "외부 비디오 ê³µìœ ", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "외부 비디오 ê³µìœ ì¤‘ì§€ ", "app.iOSWarning.label": "iOS 12.2 ì´ìƒìœ¼ë¡œ ì—…ê·¸ë ˆì´ë“œ 하세요 ", diff --git a/bigbluebutton-html5/private/locales/lo_LA.json b/bigbluebutton-html5/private/locales/lo_LA.json new file mode 100644 index 0000000000000000000000000000000000000000..822ac70f8d08905fe34bb5d88400ea5dc8e170b1 --- /dev/null +++ b/bigbluebutton-html5/private/locales/lo_LA.json @@ -0,0 +1,46 @@ +{ + "app.home.greeting": "àºàº²àº™àº™àº³àºªàº°à»€à»œàºµàº‚àºàº‡àº—່ານຈະເລີ່ມຕົ້ນໄວໆນີ້ ...", + "app.chat.submitLabel": "ສົ່ງຂà»à»‰àº„ວາມ", + "app.chat.errorMaxMessageLength": "ຂà»à»‰àº„ວາມທີ່ມີຄວາມàºàº²àº§ {0} ໂຕàºàº±àºàºªàºàº™à»àº¡à»ˆàº™àºàº²àº§à»€àºàºµàº™à»„ປ", + "app.chat.disconnected": "ທ່ານໄດ້ຫຼຸດàºàºàºàºˆàº²àºàºàº²àº™à»€àºŠàº·à»ˆàºàº¡àº•à»à»ˆ, ຂà»à»‰àº„ວາມບà»à»ˆàºªàº²àº¡àº²àº”ສົ່ງàºàºàºà»„ດ້", + "app.chat.locked": "àºàº²àº™àºªàº»àº™àº—ະນາà»àº¡à»ˆàº™àº–ືàºàº¥à»‹àºàº, ຂà»à»‰àº„ວາມບà»à»ˆàºªàº²àº¡àº²àº”ສົ່ງàºàºàºà»„ດ້", + "app.chat.inputLabel": "ປ້àºàº™àº‚à»à»‰àº„ວາມເພື່àºàºªàº»àº™àº—ະນາ {0}", + "app.chat.inputPlaceholder": "ສົ່ງຂà»à»‰àº„ວາມຫາ {0}", + "app.chat.titlePublic": "ຫ້àºàº‡àºªàº»àº™àº—ະນາສາທາລະນະ", + "app.chat.titlePrivate": "ສົນທະນາà»àºšàºšàºªà»ˆàº§àº™àº•ົວàºàº±àºš {0}", + "app.chat.partnerDisconnected": "{0} ໄດ້àºàºàºàºˆàº²àºàº«à»‰àºàº‡àº›àº°àºŠàº¸àº¡", + "app.chat.closeChatLabel": "ປິດ {0}", + "app.chat.hideChatLabel": "ເຊື່àºàº‡ {0}", + "app.chat.moreMessages": "ຂà»à»‰àº„ວາມເພີ່ມເຕີມທາງດ້ານລຸ່ມ", + "app.chat.dropdown.options": "ຕົວເລືàºàºàºàº²àº™àºªàº»àº™àº—ະນາ", + "app.chat.dropdown.clear": "ລຶບ", + "app.chat.dropdown.copy": "ສຳເນົາ", + "app.chat.dropdown.save": "ບັນທຶàº", + "app.chat.label": "ສົນທະນາ", + "app.chat.offline": "ບà»à»ˆàº¡àºµàºªàº±àº™àºàº²àº™à»€àºŠàº·à»ˆàºàº¡àº•à»à»ˆ", + "app.chat.emptyLogLabel": "ຂà»à»‰àº„ວາມສົນທະນາວ່າງເປົ່າ", + "app.chat.clearPublicChatMessage": "ຂà»à»‰àº„ວາມສົນທະນາທີ່ຜ່ານມາຖືàºàº¥àº¶àºšà»‚ດàºàºœàº¹à»‰à»€àºšàº´à»ˆàº‡à»àºàº‡àº¥àº°àºšàº»àºš", + "app.chat.multi.typing": "ບັນດາຜູ້ໃຊ້ງານàºàº³àº¥àº±àº‡àºžàº´àº¡", + "app.chat.one.typing": "{0} àºàº³àº¥àº±àº‡àºžàº´àº¡", + "app.chat.two.typing": "{0} à»àº¥àº° {1} àºàº³àº¥àº±àº‡àºžàº´àº¡", + "app.captions.label": "ຄຳບັນàºàº²àº", + "app.captions.menu.close": "ປິດ", + "app.captions.menu.start": "ເລີ່ມຕົ້ນ", + "app.captions.menu.ariaStart": "ເລີ່ມຕົ້ນຂຽນຄຳບັນàºàº²àº", + "app.captions.menu.ariaStartDesc": "ເປີດໜ້າà»àºà»‰à»„ຂຄຳບັນàºàº²àº à»àº¥àº° ປິດໜ້າຕ່າງ", + "app.captions.menu.select": "ເລືàºàºàºžàº²àºªàº²àº—ີ່ສາມາດໃຊ້ງານໄດ້", + "app.captions.menu.ariaSelect": "ພາສາຂàºàº‡àº„ຳບັນàºàº²àº", + "app.userList.captionsTitle": "ຄຳບັນàºàº²àº", + "app.poll.closeLabel": "ປິດ", + "app.settings.main.save.label": "ບັນທຶàº", + "app.audioNotification.closeLabel": "ປິດ", + "app.audioModal.closeLabel": "ປິດ", + "app.audio.listenOnly.closeLabel": "ປິດ", + "app.modal.close": "ປິດ", + "app.dropdown.close": "ປິດ", + "app.shortcut-help.closeLabel": "ປິດ", + "app.videoPreview.closeLabel": "ປິດ", + "app.externalVideo.close": "ປິດ" + +} + diff --git a/bigbluebutton-html5/private/locales/lt_LT.json b/bigbluebutton-html5/private/locales/lt_LT.json index 0beb49c7dcb5ef2e1830e042719aa2e2087fa651..fdc80818ba29e7a2a0a2e497f922bae990a7e750 100644 --- a/bigbluebutton-html5/private/locales/lt_LT.json +++ b/bigbluebutton-html5/private/locales/lt_LT.json @@ -50,10 +50,10 @@ "app.note.title": "Bendri užraÅ¡ai", "app.note.label": "UžraÅ¡ai", "app.note.hideNoteLabel": "PaslÄ—pti užrašą", + "app.note.tipLabel": "Paspauskite ESC kad sufokusuoti redaktoriaus įrankinÄ™.", "app.user.activityCheck": "Vartotojo aktyvumo patikra", "app.user.activityCheck.label": "Patikrina ar vartotojas vis dar yra susitikime ({0})", "app.user.activityCheck.check": "Patikrinti", - "app.note.tipLabel": "Paspauskite ESC kad sufokusuoti redaktoriaus įrankinÄ™.", "app.userList.usersTitle": "Vartotojai", "app.userList.participantsTitle": "Dalyviai", "app.userList.messagesTitle": "ŽinutÄ—s", @@ -63,6 +63,7 @@ "app.userList.presenter": "VedÄ—jas", "app.userList.you": "JÅ«s", "app.userList.locked": "Užrakinta", + "app.userList.byModerator": "(Moderatoriaus) ", "app.userList.label": "Vartotojų sÄ…raÅ¡as", "app.userList.toggleCompactView.label": "Perjungti kompaktiÅ¡kÄ… rodinį", "app.userList.guest": "SveÄias", @@ -73,6 +74,7 @@ "app.userList.menu.clearStatus.label": "IÅ¡valyti bÅ«senÄ…", "app.userList.menu.removeUser.label": "PaÅ¡alinti vartotojÄ…", "app.userList.menu.removeConfirmation.label": "PaÅ¡alinti naudotojÄ… ({0})", + "app.userlist.menu.removeConfirmation.desc": "Apsaugoti šį naudotojÄ… nuo pakartotinÄ—s sesijos. ", "app.userList.menu.muteUserAudio.label": "Nutildyti vartotojÄ…", "app.userList.menu.unmuteUserAudio.label": "Atitildyti vartotojÄ…", "app.userList.userAriaLabel": "{0} {1} {2} Statusas {3}", @@ -84,11 +86,19 @@ "app.userList.userOptions.manageUsersLabel": "Tvarkyti naudotojus ", "app.userList.userOptions.muteAllLabel": "Nutildyti visus naudotojus", "app.userList.userOptions.muteAllDesc": "Nutildyti visus naudotojus susitikime. ", + "app.userList.userOptions.clearAllLabel": "Naikinti visas statuso piktogramas", + "app.userList.userOptions.muteAllExceptPresenterLabel": "Nutildyti visus naudotojus, iÅ¡skyrus pranešėjÄ…", + "app.userList.userOptions.lockViewersLabel": "Užrakinti žiÅ«rovus ", + "app.userList.userOptions.disablePrivChat": "Privatus susiraÅ¡inÄ—jimas iÅ¡jungtas ", + "app.userList.userOptions.enableCam": "Dalyvių kameros yra įjungtos ", + "app.userList.userOptions.enableMic": "Dalyvių mikrofonai yra įjungti ", + "app.userList.userOptions.enablePrivChat": "Privatus susiraÅ¡inÄ—jimas yra įjungtas ", "app.userList.userOptions.enablePubChat": "VieÅ¡asis susiraÅ¡inÄ—jimas yra įjungtas", + "app.userList.userOptions.showUserList": "Naudotojų sÄ…rasas yra rodomas dalyviams", "app.media.label": "Medija", "app.media.screenshare.start": "Ekrano dalinimasis prasidÄ—jo ", + "app.media.screenshare.autoplayAllowLabel": "ŽiÅ«rÄ—ti pasidalintÄ… ekranÄ…", "app.meeting.ended": "Sesija pasibaigÄ—", - "app.meeting.alertMeetingEndsUnderOneMinute": "Susitikimas uždaromas minutÄ—s bÄ—gyje. ", "app.presentation.hide": "SlÄ—pti prezentacija", "app.presentation.slideContent": "SkaidrÄ—s turinys", "app.presentation.presentationToolbar.noNextSlideDesc": "Prezentacijos pabaiga", @@ -147,22 +157,35 @@ "app.navBar.settingsDropdown.hotkeysLabel": "KlaviatÅ«ros greitieji klaviÅ¡ai ", "app.navBar.settingsDropdown.helpLabel": "Pagalba", "app.navBar.settingsDropdown.endMeetingLabel": "Baigti susitikimÄ… ", + "app.navBar.recording": "Å i sesija yra įraÅ¡oma ", "app.navBar.recording.on": "Ä®raÅ¡as ", "app.navBar.recording.off": "NeįraÅ¡inÄ—jama", + "app.navBar.emptyAudioBrdige": "NÄ—ra aktyvaus mikrofono. DalinkitÄ—s mikrofonu, kad pridÄ—tumÄ—te garsÄ… Å¡iam įraÅ¡ui. ", + "app.leaveConfirmation.confirmLabel": "Palikti", "app.endMeeting.title": "Baigti susitikimÄ… ", "app.endMeeting.yesLabel": "Taip", "app.endMeeting.noLabel": "Ne", "app.about.title": "Apie", + "app.about.confirmLabel": "Gerai", + "app.about.confirmDesc": "Gerai", "app.about.dismissLabel": "AtÅ¡aukti", + "app.actionsBar.changeStatusLabel": "Pakeisti statusÄ…", "app.actionsBar.muteLabel": "Nutildyti", + "app.actionsBar.unmuteLabel": "Nebetildyti", + "app.actionsBar.camOffLabel": "IÅ¡jungti kamerÄ…", "app.actionsBar.raiseLabel": "Kelti", + "app.actionsBar.label": "Veiksmų eilutÄ— ", + "app.actionsBar.actionsDropdown.restorePresentationLabel": "Atkurti pristatymÄ… ", + "app.screenshare.screenShareLabel" : "Ekrano dalijimasis ", "app.submenu.application.animationsLabel": "Animacijos ", "app.submenu.application.fontSizeControlLabel": "Å rifto dydis ", "app.submenu.application.languageOptionLabel": "Pasirinkti kalbÄ…", + "app.submenu.video.videoQualityLabel": "Vaizdo įraÅ¡o kokybÄ—", "app.settings.usersTab.label": "Dalyviai", "app.settings.main.label": "Nuostatos ", "app.settings.main.cancel.label": "AtÅ¡aukti", "app.settings.main.save.label": "IÅ¡saugoti", + "app.settings.dataSavingTab.webcam": "Ä®galinti kameras", "app.actionsBar.actionsDropdown.saveUserNames": "IÅ¡saugoti naudotojų vardus", "app.actionsBar.emojiMenu.raiseHandLabel": "Kelti", "app.actionsBar.emojiMenu.sadLabel": "LiÅ«dnas", @@ -174,6 +197,7 @@ "app.audioModal.closeLabel": "Uždaryti", "app.audioModal.yes": "Taip", "app.audioModal.no": "Ne", + "app.audioModal.settingsTitle": "Keisti jÅ«sų garso nustatymus", "app.audioModal.connecting": "Jungiamasi", "app.audio.playSoundLabel": "Groti garsÄ…", "app.audio.backLabel": "Atgal", @@ -181,24 +205,47 @@ "app.audio.listenOnly.backLabel": "Atgal", "app.audio.listenOnly.closeLabel": "Uždaryti", "app.modal.close": "Uždaryti", + "app.modal.confirm": "Baigta", + "app.modal.newTab": "(atidarys naujÄ… skirtukÄ…) ", "app.dropdown.close": "Uždaryti", "app.error.404": "Nerasta", "app.error.410": "Susitikimas baigÄ—si", "app.error.leaveLabel": "Prisijungti dar kartÄ… ", "app.userList.guest.waitingUsers": "Laukiama naudotojų", + "app.userList.guest.allowAllGuests": "Leisti visus sveÄius ", + "app.userList.guest.allowEveryone": "Leisti visus", + "app.userList.guest.denyEveryone": "Atmesti visus", + "app.userList.guest.pendingUsers": "{0} Laukiantys naudotojai", + "app.userList.guest.pendingGuestUsers": "{0} Laukiantys naudotojai sveÄiai ", + "app.toast.chat.system": "Sistema", + "app.notification.recordingAriaLabel": "Ä®raÅ¡o laikas", "app.shortcut-help.title": "KlaviatÅ«ros greitieji klaviÅ¡ai ", + "app.shortcut-help.functionLabel": "Funkcija ", "app.shortcut-help.closeLabel": "Uždaryti", + "app.shortcut-help.openOptions": "Atidaryti nuostatas", + "app.shortcut-help.toggleMute": "Tildyti/Nebetildyti", + "app.shortcut-help.hidePrivateChat": "SlÄ—pti privatų susiraÅ¡inÄ—jimÄ… ", + "app.shortcut-help.closePrivateChat": "Uždaryti privatų susiraÅ¡inÄ—jimÄ… ", + "app.shortcut-help.openActions": "Atidaryti veiksmų meniu", + "app.shortcut-help.openStatus": "Atidaryti statusų meniu", + "app.shortcut-help.nextSlideDesc": "Sekanti skaidrÄ— (Pranešėjas) ", + "app.shortcut-help.previousSlideDesc": "AnkstesnÄ— skaidrÄ— (Pranešėjas) ", + "app.lock-viewers.title": "Užrakinti žiÅ«rovus ", "app.lock-viewers.lockStatusLabel": "Statusas", "app.lock-viewers.webcamLabel": "Dalintis kamera", + "app.lock-viewers.otherViewersWebcamLabel": "Matyti kitų dalyvių kameras", + "app.lock-viewers.microphoneLable": "Dalintis mikrofonu ", "app.lock-viewers.button.cancel": "AtÅ¡aukti", "app.lock-viewers.locked": "Užrakinta", "app.videoPreview.cameraLabel": "Kamera", "app.videoPreview.cancelLabel": "AtÅ¡aukti", "app.videoPreview.closeLabel": "Uždaryti", + "app.videoPreview.findingWebcamsLabel": "IeÅ¡koma kamerų", "app.videoPreview.webcamNotFoundLabel": "Kamera nerasta", "app.video.joinVideo": "Dalintis kamera", "app.video.cancel": "AtÅ¡aukti", "app.video.videoButtonDesc": "Dalintis kamera", + "app.meeting.endNotification.ok.label": "Gerai", "app.whiteboard.toolbar.tools": "Ä®rankiai", "app.whiteboard.toolbar.tools.pencil": "PieÅ¡tukas ", "app.whiteboard.toolbar.tools.triangle": "Trikampis ", @@ -219,6 +266,7 @@ "app.createBreakoutRoom.room": "Kambarys {0}", "app.createBreakoutRoom.join": "Prisijungti prie kambario ", "app.createBreakoutRoom.numberOfRooms": "Kambarių skaiÄius ", + "app.createBreakoutRoom.doneLabel": "Baigta", "app.createBreakoutRoom.roomTime": "{0} minutÄ—s", "app.externalVideo.start": "Dalintis nauju vaizdo įraÅ¡u", "app.externalVideo.close": "Uždaryti" diff --git a/bigbluebutton-html5/private/locales/lv.json b/bigbluebutton-html5/private/locales/lv.json index 15e95d9a5c0535f05c7a186eaf4b115137da83dd..150e0ee2eac20b3ba6153f9b6408aaa82ba05328 100644 --- a/bigbluebutton-html5/private/locales/lv.json +++ b/bigbluebutton-html5/private/locales/lv.json @@ -50,10 +50,10 @@ "app.note.title": "KopÄ«gÄs piezÄ«mes", "app.note.label": "PiezÄ«me", "app.note.hideNoteLabel": "NerÄdÄ«t piezÄ«mi", + "app.note.tipLabel": "Nospiediet ESC lai fokusÄ“tos uz redaktora rÄ«krindu", "app.user.activityCheck": "DalÄ«bnieka aktivitÄtes pÄrbaude", "app.user.activityCheck.label": "PÄrbaudÄ«t, vai dalÄ«bnieks joprojÄm ir sapulcÄ“ ({0})", "app.user.activityCheck.check": "PÄrbaudÄ«t", - "app.note.tipLabel": "Nospiediet ESC lai fokusÄ“tos uz redaktora rÄ«krindu", "app.userList.usersTitle": "DalÄ«bnieki", "app.userList.participantsTitle": "DalÄ«bnieki", "app.userList.messagesTitle": "Ziņas", @@ -121,8 +121,6 @@ "app.meeting.meetingTimeRemaining": "AtlikuÅ¡ais sapulces laiks: {0}", "app.meeting.meetingTimeHasEnded": "Laiks beidzies. Sapulce drÄ«z tiks slÄ“gta", "app.meeting.endedMessage": "JÅ«s tiksiet pÄrsÅ«tÄ«ts atpakaļ uz sÄkuma ekrÄnu", - "app.meeting.alertMeetingEndsUnderOneMinute": "Sapulce tiks slÄ“gta pÄ“c minÅ«tes.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Grupas telpas laiks beidzas pÄ“c minÅ«tes.", "app.presentation.hide": "NerÄdÄ«t/paslÄ“pt prezentÄciju", "app.presentation.notificationLabel": "PaÅ¡reizÄ“jÄ prezentÄcija", "app.presentation.slideContent": "Slaidu saturs", @@ -259,7 +257,6 @@ "app.leaveConfirmation.confirmLabel": "Pamest", "app.leaveConfirmation.confirmDesc": "JÅ«s izliek no sapulces", "app.endMeeting.title": "Beigt sapulci", - "app.endMeeting.description": "PatieÅ¡Äm vÄ“laties beigt Å¡o sesiju?", "app.endMeeting.yesLabel": "JÄ", "app.endMeeting.noLabel": "NÄ“", "app.about.title": "Par", @@ -280,10 +277,6 @@ "app.screenshare.screenShareLabel" : "EkrÄna kopÄ«goÅ¡ana", "app.submenu.application.applicationSectionTitle": "Lietotne", "app.submenu.application.animationsLabel": "AnimÄcijas", - "app.submenu.application.audioAlertLabel": "Audio paziņojumi tÄ“rzÄ“tavai", - "app.submenu.application.pushAlertLabel": "UznirstoÅ¡ie paziņojumi tÄ“rzÄ“tavai", - "app.submenu.application.userJoinAudioAlertLabel": "Audio paziņojumi par dalÄ«bnieka pievienoÅ¡anos", - "app.submenu.application.userJoinPushAlertLabel": "UznirstoÅ¡ie paziņojumi par dalÄ«bnieka pievienoÅ¡anos", "app.submenu.application.fontSizeControlLabel": "Å rifta izmÄ“rs", "app.submenu.application.increaseFontBtnLabel": "PalielinÄt lietotnes Å¡rifta izmÄ“ru", "app.submenu.application.decreaseFontBtnLabel": "SamazinÄt lietotnes Å¡rifta izmÄ“ru", @@ -340,25 +333,25 @@ "app.actionsBar.actionsDropdown.takePresenterDesc": "NozÄ«mÄ“t sevi kÄ jauno runÄtÄju/ziņotÄju/prezentÄ“tÄju", "app.actionsBar.emojiMenu.statusTriggerLabel": "IestatÄ«t statusu", "app.actionsBar.emojiMenu.awayLabel": "prombÅ«tnÄ“", - "app.actionsBar.emojiMenu.awayDesc": "nomainÄ«t statusu uz \"prombÅ«tnÄ“\"", + "app.actionsBar.emojiMenu.awayDesc": "nomainÄ«t statusu uz 'prombÅ«tnÄ“'", "app.actionsBar.emojiMenu.raiseHandLabel": "pacelt", "app.actionsBar.emojiMenu.raiseHandDesc": "pacelt roku, lai izteiktu jautÄjumu, repliku, komentÄru", "app.actionsBar.emojiMenu.neutralLabel": "svÄrstÄ«gs/neizlÄ“mis", - "app.actionsBar.emojiMenu.neutralDesc": "nomainÄ«t statusu uz \"svÄrstÄ«gs/neizlÄ“mis\"", + "app.actionsBar.emojiMenu.neutralDesc": "nomainÄ«t statusu uz 'svÄrstÄ«gs/neizlÄ“mis'", "app.actionsBar.emojiMenu.confusedLabel": "samulsis", - "app.actionsBar.emojiMenu.confusedDesc": "nomainÄ«t statusu uz \"samulsis\"", + "app.actionsBar.emojiMenu.confusedDesc": "nomainÄ«t statusu uz 'samulsis'", "app.actionsBar.emojiMenu.sadLabel": "skumjÅ¡", - "app.actionsBar.emojiMenu.sadDesc": "nomainÄ«t statusu uz \"skumjÅ¡\"", + "app.actionsBar.emojiMenu.sadDesc": "nomainÄ«t statusu uz 'skumjÅ¡'", "app.actionsBar.emojiMenu.happyLabel": "laimÄ«gs", - "app.actionsBar.emojiMenu.happyDesc": "nomainÄ«t statusu uz \"laimÄ«gs\"", + "app.actionsBar.emojiMenu.happyDesc": "nomainÄ«t statusu uz 'laimÄ«gs'", "app.actionsBar.emojiMenu.noneLabel": "NotÄ«rÄ«t statusu", "app.actionsBar.emojiMenu.noneDesc": "NotÄ«ri savu statusu", "app.actionsBar.emojiMenu.applauseLabel": "applaudÄ“joÅ¡s", - "app.actionsBar.emojiMenu.applauseDesc": "nomainÄ«t statusu uz \"aplaudÄ“joÅ¡s\"", + "app.actionsBar.emojiMenu.applauseDesc": "nomainÄ«t statusu uz 'aplaudÄ“joÅ¡s'", "app.actionsBar.emojiMenu.thumbsUpLabel": "Ä«kšķi augÅ¡Ä", - "app.actionsBar.emojiMenu.thumbsUpDesc": "nomainÄ«t statusu uz \"Ä«kšķi augÅ¡Ä\"", + "app.actionsBar.emojiMenu.thumbsUpDesc": "nomainÄ«t statusu uz 'Ä«kšķi augÅ¡Ä'", "app.actionsBar.emojiMenu.thumbsDownLabel": "Ä«kšķi lejÄ", - "app.actionsBar.emojiMenu.thumbsDownDesc": "nomainÄ«t statusu uz \"Ä«kšķi lejÄ\"", + "app.actionsBar.emojiMenu.thumbsDownDesc": "nomainÄ«t statusu uz 'Ä«kšķi lejÄ'", "app.actionsBar.currentStatusDesc": "paÅ¡reizÄ“jais statuss {0}", "app.actionsBar.captions.start": "SÄkt slÄ“gto aprakstu skatīšanu", "app.actionsBar.captions.stop": "Beigt slÄ“gto aprakstu skatīšanu", @@ -414,7 +407,7 @@ "app.audioModal.playAudio": "Atskaņot audio", "app.audioModal.playAudio.arialabel" : "Atskaņot audio", "app.audioDial.tipIndicator": "Padoms", - "app.audioDial.tipMessage": "Lai izslÄ“gtu / ieslÄ“gtu skaņu, tÄlrunÄ« nospiediet taustiņu \"0\".", + "app.audioDial.tipMessage": "Lai izslÄ“gtu / ieslÄ“gtu skaņu, tÄlrunÄ« nospiediet taustiņu '0'.", "app.audioModal.connecting": "Notiek savienoÅ¡anÄs...", "app.audioModal.connectingEchoTest": "Notiek savienoÅ¡anÄs ar mikrofona (atbalss) testu", "app.audioManager.joinedAudio": "JÅ«s esat pievienojies sapulcei ar audio", @@ -565,19 +558,6 @@ "app.video.videoMenuDesc": "Atveriet nolaižamo Video izvÄ“lni", "app.video.chromeExtensionError": "Jums jÄuzinstalÄ“", "app.video.chromeExtensionErrorLink": "Å¡o Chromium paplaÅ¡inÄjumu ", - "app.video.stats.title": "Savienojuma statistika", - "app.video.stats.packetsReceived": "Paketes saņemtas", - "app.video.stats.packetsSent": "Paketes nosÅ«tÄ«tas", - "app.video.stats.packetsLost": "Paketes zuduÅ¡as", - "app.video.stats.bitrate": "Bitreits", - "app.video.stats.lostPercentage": "KopÄ zaudÄ“tas %", - "app.video.stats.lostRecentPercentage": "Nesen zaudÄ“tas %", - "app.video.stats.dimensions": "Dimensijas/izmÄ“ri", - "app.video.stats.codec": "Kodeks", - "app.video.stats.decodeDelay": "Dekodēšanas iekave", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Kodēšanas (encode) lietojums", - "app.video.stats.currentDelay": "PaÅ¡reizÄ“jÄ iekave", "app.fullscreenButton.label": "{0} rÄdÄ«t pilnekrÄna režīmÄ", "app.deskshare.iceConnectionStateError": "Savienojums neizdevÄs, kopÄ«gojot ekrÄnu (ICE kļūme 1108)", "app.sfu.mediaServerConnectionError2000": "Nevar izveidot savienojumu ar multimediju serveri (kļūme 2000)", @@ -590,7 +570,6 @@ "app.sfu.invalidSdp2202":"Klients Ä£enerÄ“ja nederÄ«gu mediju pieprasÄ«jumu (SDP kļūme 2202)", "app.sfu.noAvailableCodec2203": "Serveris nevarÄ“ja atrast piemÄ“rotu kodeku (kļūme 2203)", "app.meeting.endNotification.ok.label": "Labi", - "app.whiteboard.annotations.poll": "Aptaujas/balsojuma rezultÄti tika publiskoti", "app.whiteboard.toolbar.tools": "RÄ«ki", "app.whiteboard.toolbar.tools.hand": "Aplis", "app.whiteboard.toolbar.tools.pencil": "ZÄ«mulis", @@ -671,13 +650,12 @@ "app.externalVideo.autoPlayWarning": "Atskaņojiet video, lai iespÄ“jotu multimediju sinhronizÄciju", "app.network.connection.effective.slow": "ir pamanÄ«tas savienojuma problÄ“mas.", "app.network.connection.effective.slow.help": "VairÄk informÄcijas", - "app.externalVideo.noteLabel": "PiezÄ«me: kopÄ«gotie ÄrÄ“jie videoklipi nebÅ«s redzami ierakstÄ. Tiek atbalstÄ«ti Twitch, Vimeo, YouTube, Instructure Media un Daily Motion URL.", "app.actionsBar.actionsDropdown.shareExternalVideo": "KopÄ«got ÄrÄ“jo videoklipu", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Beigt kopÄ«got ÄrÄ“jo videoklipu", "app.iOSWarning.label": "LÅ«dzu, augstiniet uz iOS 12.2 vai augstÄku versiju", "app.legacy.unsupportedBrowser": "IzskatÄs, ka izmantojat neatbalstÄ«tu pÄrlÅ«ku. PilnÄ«ga atbalsta iegūšanai izmantojiet {0} vai {1}.", "app.legacy.upgradeBrowser": "IzskatÄs, ka izmantojat atbalstÄ«ta pÄrlÅ«ka novecojuÅ¡u versiju. Lai iegÅ«tu pilnÄ«gu atbalstu, lÅ«dzu, aktualizÄ“jiet savu pÄrlÅ«kprogrammu.", - "app.legacy.criosBrowser": "IOS ierÄ«cÄ“ pilna atbalsta nodroÅ¡inÄÅ¡Änai, lÅ«dzu, izmantojiet Safari." + "app.legacy.criosBrowser": "IOS ierÄ«cÄ“ pilna atbalsta nodroÅ¡inÄÅ¡Änai lÅ«dzu, izmantojiet Safari." } diff --git a/bigbluebutton-html5/private/locales/nb_NO.json b/bigbluebutton-html5/private/locales/nb_NO.json index 58bdfd9de76113d002cfa5617a9b6c32733dad6c..eb149daaadfb57fbe416f26ff318fcf4b26123cb 100644 --- a/bigbluebutton-html5/private/locales/nb_NO.json +++ b/bigbluebutton-html5/private/locales/nb_NO.json @@ -50,10 +50,10 @@ "app.note.title": "Delte notater", "app.note.label": "Notat", "app.note.hideNoteLabel": "Skjul notat", + "app.note.tipLabel": "Trykk escape for Ã¥ fokusere pÃ¥ verktøylinjen for redigering", "app.user.activityCheck": "Sjekk brukeraktivitet", "app.user.activityCheck.label": "Sjekk at bruker fortsatt er i møtet ({0})", "app.user.activityCheck.check": "Sjekk", - "app.note.tipLabel": "Trykk escape for Ã¥ fokusere pÃ¥ verktøylinjen for redigering", "app.userList.usersTitle": "Brukere", "app.userList.participantsTitle": "Deltagere", "app.userList.messagesTitle": "Meldinger", @@ -124,8 +124,6 @@ "app.meeting.meetingTimeRemaining": "Gjenværende tid: {0}", "app.meeting.meetingTimeHasEnded": "Tiden er ute. Møtet avsluttes straks", "app.meeting.endedMessage": "Du blir videreført til hjemskjermen", - "app.meeting.alertMeetingEndsUnderOneMinute": "Møtet avsluttes om ett minutt", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Grupperom avsluttes om ett minutt", "app.presentation.hide": "Skjul presentasjon", "app.presentation.notificationLabel": "NÃ¥værende presentasjon", "app.presentation.slideContent": "Sideinnhold", @@ -265,7 +263,6 @@ "app.leaveConfirmation.confirmLabel": "GÃ¥ ut", "app.leaveConfirmation.confirmDesc": "Logger deg ut av møtet", "app.endMeeting.title": "Avslutt møtet", - "app.endMeeting.description": "Er du sikker pÃ¥ at du vil avslutte dette møtet?", "app.endMeeting.yesLabel": "Ja", "app.endMeeting.noLabel": "Nei", "app.about.title": "Om", @@ -286,10 +283,6 @@ "app.screenshare.screenShareLabel" : "Skjermdeling", "app.submenu.application.applicationSectionTitle": "Applikasjon", "app.submenu.application.animationsLabel": "Animasjoner", - "app.submenu.application.audioAlertLabel": "Lydvarsling for chat", - "app.submenu.application.pushAlertLabel": "Synlige varsler for chat", - "app.submenu.application.userJoinAudioAlertLabel": "Lydvarsling nÃ¥r deltager blir med", - "app.submenu.application.userJoinPushAlertLabel": "Synlig varsling for brukerpÃ¥logging", "app.submenu.application.fontSizeControlLabel": "Tekststørrelse", "app.submenu.application.increaseFontBtnLabel": "Forstørr applikasjonens tekststørrelse", "app.submenu.application.decreaseFontBtnLabel": "Forminsk applikasjonens fontstørrelse", @@ -571,19 +564,6 @@ "app.video.videoMenuDesc": "Ã…pne videomenyen", "app.video.chromeExtensionError": "Du mÃ¥ installere", "app.video.chromeExtensionErrorLink": "denne Chrome utvidelsen", - "app.video.stats.title": "Tilkoblingsstatistikk", - "app.video.stats.packetsReceived": "Pakker mottatt", - "app.video.stats.packetsSent": "Pakker sendt", - "app.video.stats.packetsLost": "Pakker tapt", - "app.video.stats.bitrate": "Bitrate", - "app.video.stats.lostPercentage": "Total prosent tapt", - "app.video.stats.lostRecentPercentage": "Nylig prosent tapt", - "app.video.stats.dimensions": "Dimensjoner", - "app.video.stats.codec": "Codec", - "app.video.stats.decodeDelay": "Decode forsinkelse", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Encodeforbruk", - "app.video.stats.currentDelay": "NÃ¥værende forsinkelse", "app.fullscreenButton.label": "Gjør {0} fullskjerm", "app.deskshare.iceConnectionStateError": "Connection failed when sharing screen (ICE error 1108)", "app.sfu.mediaServerConnectionError2000": "Unable to connect to media server (error 2000)", @@ -596,7 +576,6 @@ "app.sfu.invalidSdp2202":"Client generated an invalid media request (SDP error 2202)", "app.sfu.noAvailableCodec2203": "Server could not find an appropriate codec (error 2203)", "app.meeting.endNotification.ok.label": "OK", - "app.whiteboard.annotations.poll": "SpørsmÃ¥lsresultatene ble publisert", "app.whiteboard.toolbar.tools": "Verktøy", "app.whiteboard.toolbar.tools.hand": "Panorer", "app.whiteboard.toolbar.tools.pencil": "Blyant", @@ -677,7 +656,6 @@ "app.externalVideo.autoPlayWarning": "Spill av videoen for Ã¥ aktivere mediasynkronisering", "app.network.connection.effective.slow": "Vi legger merke til tilkoblingsproblemer", "app.network.connection.effective.slow.help": "Mer informasjon", - "app.externalVideo.noteLabel": "Notat: Delte eksterne videoer vil ikke bli synlige i innspillingen. YouTube, Vimeo, Instructure Media, Twitch og Daily Motion URLer er godkjent.", "app.actionsBar.actionsDropdown.shareExternalVideo": "Del en ekstern video", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Stopp deling av ekstern video", "app.iOSWarning.label": "Vennligst oppgrader til iOS 12.2 eller høyere", diff --git a/bigbluebutton-html5/private/locales/nl.json b/bigbluebutton-html5/private/locales/nl.json index 9fe621ff71ee71e9dddc245a551295b7e99cf835..63165336fb363fb4b664a414c0e5506e755aeb6b 100644 --- a/bigbluebutton-html5/private/locales/nl.json +++ b/bigbluebutton-html5/private/locales/nl.json @@ -1,7 +1,7 @@ { "app.home.greeting": "Uw presentatie begint binnenkort ...", "app.chat.submitLabel": "Bericht verzenden", - "app.chat.errorMaxMessageLength": "Het bericht is {0} tekens (s) te lang", + "app.chat.errorMaxMessageLength": "Het bericht is {0} teken(s) te lang", "app.chat.disconnected": "De verbinding is verbroken, berichten kunnen niet worden verzonden", "app.chat.locked": "Chat is vergrendeld, berichten kunnen niet worden verzonden", "app.chat.inputLabel": "Berichtinvoer voor chat {0}", @@ -18,6 +18,7 @@ "app.chat.dropdown.save": "Opslaan", "app.chat.label": "Chat", "app.chat.offline": "Offline", + "app.chat.pollResult": "Peilingsresultaten", "app.chat.emptyLogLabel": "Chatlogboek leeg", "app.chat.clearPublicChatMessage": "De openbare chatgeschiedenis is gewist door een moderator", "app.chat.multi.typing": "Meerdere gebruikers zijn aan het typen", @@ -26,20 +27,20 @@ "app.captions.label": "Bijschriften", "app.captions.menu.close": "Sluiten", "app.captions.menu.start": "Starten", - "app.captions.menu.ariaStart": "Begin bijschriften te schrijven", + "app.captions.menu.ariaStart": "Start schrijven bijschriften", "app.captions.menu.ariaStartDesc": "Opent de ondertitelingseditor en sluit de modal", "app.captions.menu.select": "Selecteer beschikbare taal", "app.captions.menu.ariaSelect": "Taal ondertitels", "app.captions.menu.subtitle": "Selecteer een taal en stijlen voor ondertitels in uw sessie.", - "app.captions.menu.title": "Gesloten bijschriften", + "app.captions.menu.title": "Ondertitels", "app.captions.menu.fontSize": "Grootte", "app.captions.menu.fontColor": "Tekstkleur", - "app.captions.menu.fontFamily": "Font", + "app.captions.menu.fontFamily": "Lettertype", "app.captions.menu.backgroundColor": "Achtergrondkleur", - "app.captions.menu.previewLabel": "Preview", + "app.captions.menu.previewLabel": "Voorbeeld", "app.captions.menu.cancelLabel": "Annuleren", "app.captions.pad.hide": "Verberg ondertitels", - "app.captions.pad.tip": "Druk op Esc om de werkbalk van de editor scherp te stellen", + "app.captions.pad.tip": "Druk op Esc om de werkbalk van de editor te activeren", "app.captions.pad.ownership": "Overnemen", "app.captions.pad.ownershipTooltip": "U wordt toegewezen als de eigenaar van {0} bijschriften", "app.captions.pad.interimResult": "Tussentijdse resultaten", @@ -49,18 +50,18 @@ "app.captions.pad.dictationOffDesc": "Schakelt spraakherkenning uit", "app.note.title": "Gedeelde notities", "app.note.label": "Notitie", - "app.note.hideNoteLabel": "Notitie verbergen", + "app.note.hideNoteLabel": "Verberg notitie", + "app.note.tipLabel": "Druk op Esc om de werkbalk van de editor te activeren", "app.user.activityCheck": "Controle gebruikersactiviteit", "app.user.activityCheck.label": "Controleer of de gebruiker nog steeds in vergadering is ({0})", - "app.user.activityCheck.check": "Controleer", - "app.note.tipLabel": "Druk op Esc om de werkbalk van de editor scherp te stellen", + "app.user.activityCheck.check": "Controleren", "app.userList.usersTitle": "Gebruikers", "app.userList.participantsTitle": "Deelnemers", "app.userList.messagesTitle": "Berichten", "app.userList.notesTitle": "Notities", "app.userList.notesListItem.unreadContent": "Nieuwe inhoud is beschikbaar in het gedeelte met gedeelde notities", - "app.userList.captionsTitle": "Captions", - "app.userList.presenter": "Presenter", + "app.userList.captionsTitle": "Bijschriften", + "app.userList.presenter": "Presentator", "app.userList.you": "U", "app.userList.locked": "Vergrendeld", "app.userList.byModerator": "door (Moderator)", @@ -71,8 +72,8 @@ "app.userList.chatListItem.unreadSingular": "{0} Nieuw bericht", "app.userList.chatListItem.unreadPlural": "{0} Nieuwe berichten", "app.userList.menu.chat.label": "Start een privéchat", - "app.userList.menu.clearStatus.label": "Status wissen", - "app.userList.menu.removeUser.label": "Gebruiker verwijderen", + "app.userList.menu.clearStatus.label": "Wis status", + "app.userList.menu.removeUser.label": "Verwijder gebruiker", "app.userList.menu.removeConfirmation.label": "Verwijder gebruiker ({0})", "app.userlist.menu.removeConfirmation.desc": "Voorkom dat deze gebruiker opnieuw deelneemt aan de sessie.", "app.userList.menu.muteUserAudio.label": "Gebruiker dempen", @@ -93,8 +94,10 @@ "app.userList.userOptions.muteAllExceptPresenterDesc": "Dempt alle gebruikers in de vergadering behalve de presentator", "app.userList.userOptions.unmuteAllLabel": "Demping van vergadering uitschakelen", "app.userList.userOptions.unmuteAllDesc": "Demping van de vergadering ongedaan maken", - "app.userList.userOptions.lockViewersLabel": "Vergrendel toeschouwers", + "app.userList.userOptions.lockViewersLabel": "Vergrendel kijkers", "app.userList.userOptions.lockViewersDesc": "Bepaalde functies vergrendelen voor deelnemers aan de vergadering", + "app.userList.userOptions.connectionStatusLabel": "Verbindingsstatus", + "app.userList.userOptions.connectionStatusDesc": "Bekijk de verbindingsstatus van gebruikers", "app.userList.userOptions.disableCam": "De webcams van kijkers zijn uitgeschakeld", "app.userList.userOptions.disableMic": "Microfoons van kijkers zijn uitgeschakeld", "app.userList.userOptions.disablePrivChat": "Privéchat is uitgeschakeld", @@ -113,8 +116,8 @@ "app.media.label": "Media", "app.media.autoplayAlertDesc": "Toegang toestaan", "app.media.screenshare.start": "Screenshare is gestart", - "app.media.screenshare.end": "Screenshare is afgelopen", - "app.media.screenshare.unavailable": "Screenshare Onbeschikbaar", + "app.media.screenshare.end": "Screenshare is beëindigd", + "app.media.screenshare.unavailable": "Screenshare niet beschikbaar", "app.media.screenshare.notSupported": "Screensharing wordt niet ondersteund in deze browser.", "app.media.screenshare.autoplayBlockedDesc": "We hebben uw toestemming nodig om u het scherm van de presentator te tonen.", "app.media.screenshare.autoplayAllowLabel": "Bekijk gedeeld scherm", @@ -124,17 +127,20 @@ "app.screenshare.genericError": "Fout: er is een fout opgetreden met screensharing, probeer het opnieuw", "app.meeting.ended": "Deze sessie is beëindigd", "app.meeting.meetingTimeRemaining": "Resterende vergadertijd: {0}", - "app.meeting.meetingTimeHasEnded": "Tijd beëindigd. Vergadering wordt binnenkort gesloten", + "app.meeting.meetingTimeHasEnded": "Tijd verstreken. Vergadering wordt spoedig afgesloten", "app.meeting.endedMessage": "U wordt teruggestuurd naar het startscherm", - "app.meeting.alertMeetingEndsUnderOneMinute": "De vergadering wordt over een minuut gesloten.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Brainstormruimte sluit over een minuut.", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "Vergadering sluit over één minuut.", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "Vergadering sluit over {0} minuten.", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "Brainstormruimte sluit over {0} minuten.", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "Brainstormruimte sluit over één minuut.", "app.presentation.hide": "Presentatie verbergen", "app.presentation.notificationLabel": "Huidige presentatie", + "app.presentation.downloadLabel": "Download", "app.presentation.slideContent": "Dia inhoud", "app.presentation.startSlideContent": "Start dia-inhoud", "app.presentation.endSlideContent": "Einde van dia-inhoud", "app.presentation.emptySlideContent": "Geen inhoud voor huidige dia", - "app.presentation.presentationToolbar.noNextSlideDesc": "Einde presentatie", + "app.presentation.presentationToolbar.noNextSlideDesc": "Einde van presentatie", "app.presentation.presentationToolbar.noPrevSlideDesc": "Start van presentatie", "app.presentation.presentationToolbar.selectLabel": "Selecteer dia", "app.presentation.presentationToolbar.prevSlideLabel": "Vorige dia", @@ -153,13 +159,13 @@ "app.presentation.presentationToolbar.zoomInDesc": "Inzoomen op de presentatie", "app.presentation.presentationToolbar.zoomOutLabel": "Uitzoomen", "app.presentation.presentationToolbar.zoomOutDesc": "Uitzoomen op de presentatie", - "app.presentation.presentationToolbar.zoomReset": "Zoom resetten", + "app.presentation.presentationToolbar.zoomReset": "Zoom opnieuw instellen", "app.presentation.presentationToolbar.zoomIndicator": "Huidig zoompercentage", "app.presentation.presentationToolbar.fitToWidth": "Aanpassen aan breedte", "app.presentation.presentationToolbar.fitToPage": "Aanpassen aan pagina", "app.presentation.presentationToolbar.goToSlide": "Dia {0}", "app.presentationUploder.title": "Presentatie", - "app.presentationUploder.message": "Als presentator kunt u elk kantoordocument of PDF-bestand uploaden. We raden het PDF-bestand aan voor de beste resultaten. Zorg ervoor dat een presentatie is geselecteerd met behulp van het selectievakje aan de rechterkant. ", + "app.presentationUploder.message": "Als presentator kunt u elk Office-document of PDF-bestand uploaden. We raden het PDF-bestand aan voor de beste resultaten. Zorg ervoor dat een presentatie is geselecteerd met behulp van het selectievakje aan de rechterkant. ", "app.presentationUploder.uploadLabel": "Upload", "app.presentationUploder.confirmLabel": "Bevestigen", "app.presentationUploder.confirmDesc": "Sla uw wijzigingen op en start de presentatie", @@ -170,10 +176,11 @@ "app.presentationUploder.browseFilesLabel": "of blader naar bestanden", "app.presentationUploder.browseImagesLabel": "of bladeren / vastleggen voor afbeeldingen", "app.presentationUploder.fileToUpload": "Wordt geüpload ...", - "app.presentationUploder.currentBadge": "Current", - "app.presentationUploder.rejectedError": "De geselecteerde bestanden zijn geweigerd. Controleer de bestandstype (s).", + "app.presentationUploder.currentBadge": "Huidig", + "app.presentationUploder.rejectedError": "De geselecteerde bestanden zijn geweigerd. Controleer bestandstype(s).", "app.presentationUploder.upload.progress": "Uploaden ({0}%)", "app.presentationUploder.upload.413": "Bestand is te groot. Splitsen in meerdere bestanden.", + "app.presentationUploder.genericError": "Oeps! Er is iets misgegaan ...", "app.presentationUploder.upload.408": "Verzoek time-out voor uploadtoken.", "app.presentationUploder.upload.404": "404: Ongeldig uploadtoken", "app.presentationUploder.upload.401": "Verzoek voor presentatie uploadtoken mislukt.", @@ -186,29 +193,36 @@ "app.presentationUploder.conversion.officeDocConversionInvalid": "Kan Office-document niet verwerken. Upload in plaats daarvan een PDF.", "app.presentationUploder.conversion.officeDocConversionFailed": "Kan Office-document niet verwerken. Upload in plaats daarvan een PDF.", "app.presentationUploder.conversion.pdfHasBigPage": "We konden het PDF-bestand niet converteren, probeer het te optimaliseren", - "app.presentationUploder.conversion.timeout": "Ops, de conversie heeft te lang geduurd", + "app.presentationUploder.conversion.timeout": "Oeps, de conversie heeft te lang geduurd", "app.presentationUploder.conversion.pageCountFailed": "Bepalen van het aantal pagina's is mislukt.", - "app.presentationUploder.isDownloadableLabel": "Sta niet toe dat de presentatie wordt gedownload", - "app.presentationUploder.isNotDownloadableLabel": "Presentatie toestaan ​​om te worden gedownload", + "app.presentationUploder.isDownloadableLabel": "Sta niet toe dat presentatie wordt gedownload", + "app.presentationUploder.isNotDownloadableLabel": "Toestaan dat presentatie wordt gedownload", "app.presentationUploder.removePresentationLabel": "Presentatie verwijderen", - "app.presentationUploder.setAsCurrentPresentation": "Presentatie instellen als actueel", + "app.presentationUploder.setAsCurrentPresentation": "Presentatie instellen als huidige", "app.presentationUploder.tableHeading.filename": "Bestandsnaam", "app.presentationUploder.tableHeading.options": "Opties", "app.presentationUploder.tableHeading.status": "Status", - "app.poll.pollPaneTitle": "Polling", + "app.presentationUploder.uploading": "{0} {1} uploaden", + "app.presentationUploder.uploadStatus": "{0} van {1} uploads voltooid", + "app.presentationUploder.completed": "{0} uploads voltooid", + "app.presentationUploder.item" : "item", + "app.presentationUploder.itemPlural" : "items", + "app.presentationUploder.clearErrors": "Fouten wissen", + "app.presentationUploder.clearErrorsDesc": "Wist mislukte uploads van presentaties", + "app.poll.pollPaneTitle": "Peiling", "app.poll.quickPollTitle": "Snelle peiling", "app.poll.hidePollDesc": "Verbergt het peilmenupaneel", "app.poll.customPollInstruction": "Om een ​​aangepaste peiling te maken, selecteert u de onderstaande knop en voert u uw opties in.", "app.poll.quickPollInstruction": "Selecteer hieronder een optie om uw peiling te starten.", - "app.poll.customPollLabel": "Aangepaste poll", + "app.poll.customPollLabel": "Aangepaste peiling", "app.poll.startCustomLabel": "Start aangepaste peiling", - "app.poll.activePollInstruction": "Laat dit paneel open om live reacties op uw enquête te zien. Wanneer u klaar bent, selecteert u 'Pollingsresultaten publiceren' om de resultaten te publiceren en de poll te beëindigen.", - "app.poll.publishLabel": "Polling-resultaten publiceren", - "app.poll.backLabel": "Terug naar polling-opties", + "app.poll.activePollInstruction": "Laat dit paneel open om live reacties op uw peiling te zien. Wanneer u klaar bent, selecteert u 'Peilingsresultaten publiceren' om de resultaten te publiceren en de peiling te beëindigen.", + "app.poll.publishLabel": "Peilingsresultaten publiceren", + "app.poll.backLabel": "Terug naar peiling-opties", "app.poll.closeLabel": "Sluiten", - "app.poll.waitingLabel": "Wachten op antwoorden ({0} / {1})", - "app.poll.ariaInputCount": "Aangepaste poll-optie {0} van {1}", - "app.poll.customPlaceholder": "Poll-optie toevoegen", + "app.poll.waitingLabel": "Wachten op reacties ({0} / {1})", + "app.poll.ariaInputCount": "Aangepaste peiling-optie {0} van {1}", + "app.poll.customPlaceholder": "Peiling-optie toevoegen", "app.poll.noPresentationSelected": "Geen presentatie geselecteerd! Selecteer er één.", "app.poll.clickHereToSelect": "Klik hier om te selecteren", "app.poll.t": "Waar", @@ -231,15 +245,16 @@ "app.poll.answer.d": "D", "app.poll.answer.e": "E", "app.poll.liveResult.usersTitle": "Gebruikers", - "app.poll.liveResult.responsesTitle": "Antwoord", - "app.polling.pollingTitle": "Polling-opties", - "app.polling.pollAnswerLabel": "Poll-antwoord {0}", + "app.poll.liveResult.responsesTitle": "Reactie", + "app.polling.pollingTitle": "Peiling-opties", + "app.polling.pollAnswerLabel": "Peiling-antwoord {0}", "app.polling.pollAnswerDesc": "Selecteer deze optie om op {0} te stemmen", "app.failedMessage": "Excuses, problemen bij het verbinden met de server.", "app.downloadPresentationButton.label": "Download de originele presentatie", "app.connectingMessage": "Bezig met verbinden ...", "app.waitingMessage": "Verbinding verbroken. Probeer opnieuw verbinding te maken over {0} seconden ...", "app.retryNow": "Nu opnieuw proberen", + "app.muteWarning.label": "Klik op {0} om het dempen van jezelf op te heffen.", "app.navBar.settingsDropdown.optionsLabel": "Opties", "app.navBar.settingsDropdown.fullscreenLabel": "Volledig scherm maken", "app.navBar.settingsDropdown.settingsLabel": "Instellingen", @@ -254,20 +269,20 @@ "app.navBar.settingsDropdown.hotkeysLabel": "Sneltoetsen", "app.navBar.settingsDropdown.hotkeysDesc": "Lijst met beschikbare sneltoetsen", "app.navBar.settingsDropdown.helpLabel": "Help", - "app.navBar.settingsDropdown.helpDesc": "Koppel gebruiker naar videotutorials (opent nieuw tabblad)", + "app.navBar.settingsDropdown.helpDesc": "Koppelt gebruiker aan videotutorials (opent nieuw tabblad)", "app.navBar.settingsDropdown.endMeetingDesc": "Beëindigt de huidige vergadering", "app.navBar.settingsDropdown.endMeetingLabel": "Vergadering beëindigen", "app.navBar.userListToggleBtnLabel": "Gebruikerslijst tonen/verbergen", - "app.navBar.toggleUserList.ariaLabel": "Gebruikers en berichten schakelen", + "app.navBar.toggleUserList.ariaLabel": "Gebruikers en berichten tonen/verbergen", "app.navBar.toggleUserList.newMessages": "met melding van nieuwe berichten", "app.navBar.recording": "Deze sessie wordt opgenomen", - "app.navBar.recording.on": "Opname", + "app.navBar.recording.on": "Opnemen", "app.navBar.recording.off": "Niet opnemen", "app.navBar.emptyAudioBrdige": "Geen actieve microfoon. Deel je microfoon om audio toe te voegen aan deze opname.", "app.leaveConfirmation.confirmLabel": "Verlaten", "app.leaveConfirmation.confirmDesc": "Logt u uit van de vergadering", "app.endMeeting.title": "Vergadering beëindigen", - "app.endMeeting.description": "Weet u zeker dat u deze sessie wilt beëindigen?", + "app.endMeeting.description": "Weet u zeker dat u deze vergadering voor iedereen wilt beëindigen (de verbinding van alle gebruikers zal worden verbroken)?", "app.endMeeting.yesLabel": "Ja", "app.endMeeting.noLabel": "Nee", "app.about.title": "Over", @@ -281,17 +296,13 @@ "app.actionsBar.muteLabel": "Dempen", "app.actionsBar.unmuteLabel": "Dempen opheffen", "app.actionsBar.camOffLabel": "Camera uit", - "app.actionsBar.raiseLabel": "Raise", + "app.actionsBar.raiseLabel": "Hand opsteken", "app.actionsBar.label": "Actiesbalk", "app.actionsBar.actionsDropdown.restorePresentationLabel": "Presentatie herstellen", "app.actionsBar.actionsDropdown.restorePresentationDesc": "Knop om de presentatie te herstellen nadat deze is gesloten", "app.screenshare.screenShareLabel" : "Screen share", "app.submenu.application.applicationSectionTitle": "Applicatie", "app.submenu.application.animationsLabel": "Animaties", - "app.submenu.application.audioAlertLabel": "Hoorbare waarschuwingen voor chat", - "app.submenu.application.pushAlertLabel": "Pop-upwaarschuwingen voor chat", - "app.submenu.application.userJoinAudioAlertLabel": "Audiowaarschuwingen voor nieuwe deelnemers", - "app.submenu.application.userJoinPushAlertLabel": "Pop-upwaarschuwingen voor nieuwe deelnemers", "app.submenu.application.fontSizeControlLabel": "Lettergrootte", "app.submenu.application.increaseFontBtnLabel": "Toepassing lettertype vergroten", "app.submenu.application.decreaseFontBtnLabel": "Toepassing lettertype verkleinen", @@ -299,6 +310,12 @@ "app.submenu.application.languageLabel": "Taal van de applicatie", "app.submenu.application.languageOptionLabel": "Kies taal", "app.submenu.application.noLocaleOptionLabel": "Geen actieve landinstellingen", + "app.submenu.notification.SectionTitle": "Notificaties", + "app.submenu.notification.Desc": "Bepaal hoe en over wat u wordt gewaarschuwd.", + "app.submenu.notification.audioAlertLabel": "Hoorbare Waarschuwingen", + "app.submenu.notification.pushAlertLabel": "Pop-upwaarschuwingen", + "app.submenu.notification.messagesLabel": "Chat Bericht", + "app.submenu.notification.userJoinLabel": "Gebruiker doet mee", "app.submenu.audio.micSourceLabel": "Microfoonbron", "app.submenu.audio.speakerSourceLabel": "Luidsprekerbron", "app.submenu.audio.streamVolumeLabel": "Het volume van uw audiostream", @@ -314,16 +331,20 @@ "app.settings.usersTab.label": "Deelnemers", "app.settings.main.label": "Instellingen", "app.settings.main.cancel.label": "Annuleren", - "app.settings.main.cancel.label.description": "Gaat de wijzigingen weg en sluit het instellingenmenu", + "app.settings.main.cancel.label.description": "Gooit de wijzigingen weg en sluit het instellingenmenu", "app.settings.main.save.label": "Opslaan", "app.settings.main.save.label.description": "Slaat de wijzigingen op en sluit het instellingenmenu", "app.settings.dataSavingTab.label": "Gegevensbesparing", "app.settings.dataSavingTab.webcam": "Webcams inschakelen", "app.settings.dataSavingTab.screenShare": "Desktop delen inschakelen", "app.settings.dataSavingTab.description": "Om uw bandbreedte te besparen, kunt u aanpassen wat momenteel wordt weergegeven.", - "app.settings.save-notification.label": "Instellingen werden opgeslagen", - "app.switch.onLabel": "ON", - "app.switch.offLabel": "OFF", + "app.settings.save-notification.label": "Instellingen zijn opgeslagen", + "app.statusNotifier.lowerHands": "Handen omlaag", + "app.statusNotifier.raisedHandsTitle": "Handen omhoog", + "app.statusNotifier.raisedHandDesc": "{0} stak hun hand op", + "app.statusNotifier.and": "en", + "app.switch.onLabel": "AAN", + "app.switch.offLabel": "UIT", "app.talkingIndicator.ariaMuteDesc" : "Selecteer om gebruiker te dempen", "app.talkingIndicator.isTalking" : "{0} is aan het spreken", "app.talkingIndicator.wasTalking" : "{0} is gestopt met spreken", @@ -331,20 +352,20 @@ "app.actionsBar.actionsDropdown.presentationLabel": "Een presentatie uploaden", "app.actionsBar.actionsDropdown.initPollLabel": "Een peiling initiëren", "app.actionsBar.actionsDropdown.desktopShareLabel": "Deel uw scherm", - "app.actionsBar.actionsDropdown.lockedDesktopShareLabel": "Screenshare vergrendeld", + "app.actionsBar.actionsDropdown.lockedDesktopShareLabel": "Schermdeling vergrendeld", "app.actionsBar.actionsDropdown.stopDesktopShareLabel": "Stop met het delen van uw scherm", "app.actionsBar.actionsDropdown.presentationDesc": "Upload uw presentatie", "app.actionsBar.actionsDropdown.initPollDesc": "Een peiling initiëren", "app.actionsBar.actionsDropdown.desktopShareDesc": "Deel uw scherm met anderen", "app.actionsBar.actionsDropdown.stopDesktopShareDesc": "Stop met het delen van uw scherm met", "app.actionsBar.actionsDropdown.pollBtnLabel": "Start een peiling", - "app.actionsBar.actionsDropdown.pollBtnDesc": "Schakelt poll deelvenster", + "app.actionsBar.actionsDropdown.pollBtnDesc": "Toont/verbergt peilingspaneel", "app.actionsBar.actionsDropdown.saveUserNames": "Gebruikersnamen opslaan", "app.actionsBar.actionsDropdown.createBreakoutRoom": "Brainstormruimtes maken", - "app.actionsBar.actionsDropdown.createBreakoutRoomDesc": "breakouts maken voor het splitsen van de huidige vergadering", - "app.actionsBar.actionsDropdown.captionsLabel": "Schrijf gesloten bijschriften", - "app.actionsBar.actionsDropdown.captionsDesc": "Schakelt het deelvenster bijschriften", - "app.actionsBar.actionsDropdown.takePresenter": "Presenter nemen", + "app.actionsBar.actionsDropdown.createBreakoutRoomDesc": "brainstormruimtes maken voor het splitsen van de huidige vergadering", + "app.actionsBar.actionsDropdown.captionsLabel": "Schrijf ondertitels", + "app.actionsBar.actionsDropdown.captionsDesc": "Toont/verbergt bijschriftenpaneel", + "app.actionsBar.actionsDropdown.takePresenter": "Presentator-rol nemen", "app.actionsBar.actionsDropdown.takePresenterDesc": "Wijs uzelf toe als de nieuwe presentator", "app.actionsBar.emojiMenu.statusTriggerLabel": "Status instellen", "app.actionsBar.emojiMenu.awayLabel": "Afwezig", @@ -355,8 +376,8 @@ "app.actionsBar.emojiMenu.neutralDesc": "Wijzig uw status in onbeslist", "app.actionsBar.emojiMenu.confusedLabel": "Verward", "app.actionsBar.emojiMenu.confusedDesc": "Wijzig uw status in verward", - "app.actionsBar.emojiMenu.sadLabel": "Ongelukkig", - "app.actionsBar.emojiMenu.sadDesc": "Wijzig uw status in ongelukkig", + "app.actionsBar.emojiMenu.sadLabel": "Verdrietig", + "app.actionsBar.emojiMenu.sadDesc": "Wijzig uw status in verdrietig", "app.actionsBar.emojiMenu.happyLabel": "Gelukkig", "app.actionsBar.emojiMenu.happyDesc": "Wijzig uw status in gelukkig", "app.actionsBar.emojiMenu.noneLabel": "Status wissen", @@ -368,9 +389,9 @@ "app.actionsBar.emojiMenu.thumbsDownLabel": "Duim omlaag", "app.actionsBar.emojiMenu.thumbsDownDesc": "Wijzig uw status in duim omlaag", "app.actionsBar.currentStatusDesc": "huidige status {0}", - "app.actionsBar.captions.start": "Begin met het bekijken van ondertitels", + "app.actionsBar.captions.start": "Start met het bekijken van ondertitels", "app.actionsBar.captions.stop": "Stop met het bekijken van ondertitels", - "app.audioNotification.audioFailedError1001": "WebSocket verbroken (fout 1001)", + "app.audioNotification.audioFailedError1001": "WebSocket verbinding verbroken (fout 1001)", "app.audioNotification.audioFailedError1002": "Kon geen WebSocket-verbinding maken (fout 1002)", "app.audioNotification.audioFailedError1003": "Browserversie niet ondersteund (fout 1003)", "app.audioNotification.audioFailedError1004": "Fout bij oproep (reden = {0}) (fout 1004)", @@ -388,21 +409,21 @@ "app.audioNotificaion.reconnectingAsListenOnly": "Microfoon is vergrendeld voor kijkers, u bent verbonden als alleen luisteren", "app.breakoutJoinConfirmation.title": "Deelnemen aan brainstormruimte", "app.breakoutJoinConfirmation.message": "Wilt u deelnemen", - "app.breakoutJoinConfirmation.confirmDesc": "Ga met je mee naar de brainstormruimte", + "app.breakoutJoinConfirmation.confirmDesc": "Deelnemen aan de brainstormruimte", "app.breakoutJoinConfirmation.dismissLabel": "Annuleren", "app.breakoutJoinConfirmation.dismissDesc": "Sluit en weigert toegang tot de brainstormruimte", "app.breakoutJoinConfirmation.freeJoinMessage": "Kies een brainstormruimte om lid te worden", "app.breakoutTimeRemainingMessage": "Resterende brainstorm-tijd: {0}", - "app.breakoutWillCloseMessage": "Tijd beëindigd. Brainstormruimte wordt binnenkort gesloten", + "app.breakoutWillCloseMessage": "Tijd verstreken. Brainstormruimte wordt spoedig afgesloten", "app.calculatingBreakoutTimeRemaining": "Resterende tijd berekenen ...", - "app.audioModal.ariaTitle": "Deelnemen aan audiomodaal", + "app.audioModal.ariaTitle": "Deelnemen aan audio modaal", "app.audioModal.microphoneLabel": "Microfoon", "app.audioModal.listenOnlyLabel": "Alleen luisteren", "app.audioModal.audioChoiceLabel": "Hoe wilt u deelnemen aan de audio?", "app.audioModal.iOSBrowser": "Audio / Video niet ondersteund", "app.audioModal.iOSErrorDescription": "Op dit moment worden audio en video niet ondersteund in Chrome voor iOS.", "app.audioModal.iOSErrorRecommendation": "We raden het gebruik van Safari iOS aan.", - "app.audioModal.audioChoiceDesc": "Selecteer hoe u aan de audio deelneemt aan deze vergadering", + "app.audioModal.audioChoiceDesc": "Selecteer hoe u aan de audio deelneemt in deze vergadering", "app.audioModal.unsupportedBrowserLabel": "Het lijkt erop dat u een browser gebruikt die niet volledig wordt ondersteund. Gebruik {0} of {1} voor volledige ondersteuning.", "app.audioModal.closeLabel": "Sluiten", "app.audioModal.yes": "Ja", @@ -416,7 +437,7 @@ "app.audioModal.help.noSSL": "Deze pagina is niet beveiligd. Voor toegang tot de microfoon moet de pagina via HTTPS worden aangeboden. Neem contact op met de serverbeheerder.", "app.audioModal.help.macNotAllowed": "Het lijkt erop dat uw Mac-systeemvoorkeuren de toegang tot uw microfoon blokkeren. Open Systeemvoorkeuren> Beveiliging en privacy> Privacy> Microfoon en controleer of de browser die u gebruikt is aangevinkt.", "app.audioModal.audioDialTitle": "Doe mee met uw telefoon", - "app.audioDial.audioDialDescription": "Dial", + "app.audioDial.audioDialDescription": "Inbellen", "app.audioDial.audioDialConfrenceText": "en voer de pincode van de conferentie in:", "app.audioModal.autoplayBlockedDesc": "We hebben uw toestemming nodig om audio af te spelen.", "app.audioModal.playAudio": "Audio afspelen", @@ -444,15 +465,15 @@ "app.audio.audioSettings.microphoneSourceLabel": "Microfoonbron", "app.audio.audioSettings.speakerSourceLabel": "Luidsprekerbron", "app.audio.audioSettings.microphoneStreamLabel": "Het volume van uw audiostream", - "app.audio.audioSettings.retryLabel": "Retry", - "app.audio.listenOnly.backLabel": "Back", + "app.audio.audioSettings.retryLabel": "Opnieuw proberen", + "app.audio.listenOnly.backLabel": "Terug", "app.audio.listenOnly.closeLabel": "Sluiten", "app.audio.permissionsOverlay.title": "Toegang tot uw microfoon toestaan", - "app.audio.permissionsOverlay.hint": "We moeten u toestemming geven om uw media-apparaten te gebruiken om u bij de spraakconferentie te voegen :)", + "app.audio.permissionsOverlay.hint": "We moeten uw toestemming hebben om uw media-apparaten te gebruiken om u aan de spraakconferentie te laten deelnemen :)", "app.error.removed": "U bent verwijderd uit de conferentie", "app.error.meeting.ended": "U bent uitgelogd van de conferentie", "app.meeting.logout.duplicateUserEjectReason": "Dubbele gebruiker die aan de vergadering probeert deel te nemen", - "app.meeting.logout.permissionEjectReason": "Uitgevoerd wegens schending van de rechten", + "app.meeting.logout.permissionEjectReason": "Uitgeworpen wegens schending van de rechten", "app.meeting.logout.ejectedFromMeeting": "U bent uit de vergadering verwijderd", "app.meeting.logout.validateTokenFailedEjectReason": "Kan autorisatietoken niet valideren", "app.meeting.logout.userInactivityEjectReason": "Gebruiker te lang inactief", @@ -469,25 +490,27 @@ "app.error.403": "U bent verwijderd uit de vergadering", "app.error.404": "Niet gevonden", "app.error.410": "Vergadering is beëindigd", - "app.error.500": "Ops, er is iets misgegaan", + "app.error.500": "Oeps, er is iets misgegaan", "app.error.leaveLabel": "Log opnieuw in", "app.error.fallback.presentation.title": "Er is een fout opgetreden", "app.error.fallback.presentation.description": "Het is vastgelegd. Probeer de pagina opnieuw te laden.", "app.error.fallback.presentation.reloadButton": "Herladen", - "app.guest.waiting": "Wachten op goedkeuring", + "app.guest.waiting": "Wachten op goedkeuring om deel te nemen", "app.userList.guest.waitingUsers": "Wachtende gebruikers", "app.userList.guest.waitingUsersTitle": "Gebruikersbeheer", - "app.userList.guest.optionTitle": "Gebruikers in behandeling beoordelen", + "app.userList.guest.optionTitle": "Gebruikers in afwachting beoordelen", "app.userList.guest.allowAllAuthenticated": "Alle geverifieerde toestaan", "app.userList.guest.allowAllGuests": "Alle gasten toestaan", "app.userList.guest.allowEveryone": "Iedereen toestaan", "app.userList.guest.denyEveryone": "Iedereen weigeren", - "app.userList.guest.pendingUsers": "{0} Gebruikers in behandeling", + "app.userList.guest.pendingUsers": "{0} Gebruikers in afwachting", "app.userList.guest.pendingGuestUsers": "{0} Wachtende gastgebruikers", "app.userList.guest.pendingGuestAlert": "Heeft zich bij de sessie aangesloten en wacht op uw goedkeuring.", "app.userList.guest.rememberChoice": "Keuze onthouden", + "app.userList.guest.acceptLabel": "Accepteer", + "app.userList.guest.denyLabel": "Weiger", "app.user-info.title": "Woordenboek zoekopdracht", - "app.toast.breakoutRoomEnded": "De brainstormruimte is beëindigd. Doe opnieuw mee met de audio.", + "app.toast.breakoutRoomEnded": "De brainstormruimte is beëindigd. Neem opnieuw deel aan de audio.", "app.toast.chat.public": "Nieuw openbaar chatbericht", "app.toast.chat.private": "Nieuw privéchatbericht", "app.toast.chat.system": "Systeem", @@ -500,16 +523,17 @@ "app.notification.recordingPaused": "Deze sessie wordt niet meer opgenomen", "app.notification.recordingAriaLabel": "Opgenomen tijd", "app.notification.userJoinPushAlert": "{0} neemt deel aan de sessie", + "app.submenu.notification.raiseHandLabel": "Hand opsteken", "app.shortcut-help.title": "Sneltoetsen", "app.shortcut-help.accessKeyNotAvailable": "Toegangssleutels niet beschikbaar", "app.shortcut-help.comboLabel": "Combo", "app.shortcut-help.functionLabel": "Functie", "app.shortcut-help.closeLabel": "Sluiten", - "app.shortcut-help.closeDesc": "Sluit modale sneltoetsen", + "app.shortcut-help.closeDesc": "Sluit modal voor sneltoetsen", "app.shortcut-help.openOptions": "Open opties", - "app.shortcut-help.toggleUserList": "Schakel gebruikerlijst", - "app.shortcut-help.toggleMute": "Mute / Unmute", - "app.shortcut-help.togglePublicChat": "Schakel openbare chat (Gebruikerslijst moet open zijn)", + "app.shortcut-help.toggleUserList": "Toon/verberg gebruikerslijst", + "app.shortcut-help.toggleMute": "Dempen / Dempen opheffen", + "app.shortcut-help.togglePublicChat": "Toon/verberg openbare chat (Gebruikerslijst moet open zijn)", "app.shortcut-help.hidePrivateChat": "Verberg privéchat", "app.shortcut-help.closePrivateChat": "Sluit privéchat", "app.shortcut-help.openActions": "Actiesmenu openen", @@ -517,22 +541,28 @@ "app.shortcut-help.togglePan": "Activeer Pan-tool (Presentator)", "app.shortcut-help.nextSlideDesc": "Volgende dia (Presentator)", "app.shortcut-help.previousSlideDesc": "Vorige dia (Presentator)", - "app.lock-viewers.title": "Vergrendel toeschouwers", + "app.lock-viewers.title": "Vergrendel kijkers", "app.lock-viewers.description": "Met deze opties kunt u ervoor zorgen dat kijkers geen specifieke functies gebruiken.", - "app.lock-viewers.featuresLable": "Feature", + "app.lock-viewers.featuresLable": "Kenmerk", "app.lock-viewers.lockStatusLabel": "Status", "app.lock-viewers.webcamLabel": "Webcam delen", - "app.lock-viewers.otherViewersWebcamLabel": "Toegang andere webcams", + "app.lock-viewers.otherViewersWebcamLabel": "Bekijk andere webcams", "app.lock-viewers.microphoneLable": "Deel microfoon", - "app.lock-viewers.PublicChatLabel": "Openbare chatberichten verzenden", + "app.lock-viewers.PublicChatLabel": "Stuur openbare chatberichten", "app.lock-viewers.PrivateChatLable": "Stuur privéchatberichten", "app.lock-viewers.notesLabel": "Bewerk gedeelde notities", - "app.lock-viewers.userListLabel": "Zie andere kijkers in de gebruikerslijst", + "app.lock-viewers.userListLabel": "Bekijk andere kijkers in de gebruikerslijst", "app.lock-viewers.ariaTitle": "Modal van kijkersinstellingen vergrendelen", "app.lock-viewers.button.apply": "Toepassen", "app.lock-viewers.button.cancel": "Annuleren", "app.lock-viewers.locked": "Vergrendeld", "app.lock-viewers.unlocked": "Ontgrendeld", + "app.connection-status.ariaTitle": "Verbindingsstatus modal", + "app.connection-status.title": "Verbindingsstatus", + "app.connection-status.description": "Bekijk de verbindingsstatus van gebruikers", + "app.connection-status.empty": "Er is tot nu toe geen verbindingsprobleem gemeld", + "app.connection-status.more": "meer", + "app.connection-status.offline": "offline", "app.recording.startTitle": "Opname starten", "app.recording.stopTitle": "Opname onderbreken", "app.recording.resumeTitle": "Opname hervatten", @@ -540,10 +570,17 @@ "app.recording.stopDescription": "Weet u zeker dat u de opname wilt pauzeren? U kunt later doorgaan door de opnieuw op de opnameknop te klikken.", "app.videoPreview.cameraLabel": "Camera", "app.videoPreview.profileLabel": "Kwaliteit", + "app.videoPreview.quality.low": "Laag", + "app.videoPreview.quality.medium": "Medium", + "app.videoPreview.quality.high": "Hoog", + "app.videoPreview.quality.hd": "Hoge kwaliteit", "app.videoPreview.cancelLabel": "Annuleren", "app.videoPreview.closeLabel": "Sluiten", "app.videoPreview.findingWebcamsLabel": "Webcams zoeken", "app.videoPreview.startSharingLabel": "Beginnen met delen", + "app.videoPreview.stopSharingLabel": "Stop met delen", + "app.videoPreview.stopSharingAllLabel": "Alle stoppen", + "app.videoPreview.sharedCameraLabel": "Deze camera wordt al gedeeld", "app.videoPreview.webcamOptionLabel": "Kies webcam", "app.videoPreview.webcamPreviewLabel": "Webcamvoorbeeld", "app.videoPreview.webcamSettingsTitle": "Webcaminstellingen", @@ -558,9 +595,9 @@ "app.video.notFoundError": "Kon de webcam niet vinden. Controleer of deze is verbonden", "app.video.notAllowed": "Ontbrekende toestemming voor gedeelde webcam, zorg ervoor dat uw browsermachtigingen", "app.video.notSupportedError": "Kan webcamvideo alleen delen met veilige bronnen, zorg ervoor dat uw SSL-certificaat geldig is", - "app.video.notReadableError": "Kon geen webcamvideo krijgen. Zorg ervoor dat een ander programma de webcam niet gebruikt", + "app.video.notReadableError": "Kon geen webcamvideo ophalen. Zorg ervoor dat andere programma's de webcam niet gebruiken", "app.video.mediaFlowTimeout1020": "Media konden de server niet bereiken (fout 1020)", - "app.video.suggestWebcamLock": "Lock-instelling afdwingen voor webcams van kijkers?", + "app.video.suggestWebcamLock": "Vergrendel-instelling afdwingen voor webcams van kijkers?", "app.video.suggestWebcamLockReason": "(dit zal de stabiliteit van de vergadering verbeteren)", "app.video.enable": "Inschakelen", "app.video.cancel": "Annuleren", @@ -573,24 +610,13 @@ "app.video.videoMenuDesc": "Vervolgkeuzelijst Videomenu openen", "app.video.chromeExtensionError": "U moet installeren", "app.video.chromeExtensionErrorLink": "deze Chrome-extensie", - "app.video.stats.title": "Verbindingsstatistieken", - "app.video.stats.packetsReceived": "Pakketten ontvangen", - "app.video.stats.packetsSent": "Pakketten verzonden", - "app.video.stats.packetsLost": "Pakketten verloren", - "app.video.stats.bitrate": "Bitrate", - "app.video.stats.lostPercentage": "Totaal verloren percentage", - "app.video.stats.lostRecentPercentage": "Recent percentage verloren", - "app.video.stats.dimensions": "Dimensions", - "app.video.stats.codec": "Codec", - "app.video.stats.decodeDelay": "Decodeer vertraging", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Gebruik coderen", - "app.video.stats.currentDelay": "Huidige vertraging", + "app.video.pagination.prevPage": "Zie vorige videos", + "app.video.pagination.nextPage": "Zie volgende videos", "app.fullscreenButton.label": "Maak {0} volledig scherm", "app.deskshare.iceConnectionStateError": "Verbinding mislukt tijdens delen van scherm (ICE error 1108)", "app.sfu.mediaServerConnectionError2000": "Kan geen verbinding maken met mediaserver (fout 2000)", "app.sfu.mediaServerOffline2001": "Mediaserver is offline. Probeer het later opnieuw (fout 2001)", - "app.sfu.mediaServerNoResources2002": "Mediaserver heeft geen beschikbare middelen (fout 2002)", + "app.sfu.mediaServerNoResources2002": "Mediaserver heeft geen beschikbare bronnen (fout 2002)", "app.sfu.mediaServerRequestTimeout2003": "Mediaserververzoeken duren te lang (fout 2003)", "app.sfu.serverIceGatheringFailed2021": "Mediaserver kan geen verbindingskandidaten verzamelen (ICE error 2021)", "app.sfu.serverIceGatheringFailed2022": "Mediaserververbinding mislukt (ICE-fout 2022)", @@ -598,7 +624,8 @@ "app.sfu.invalidSdp2202":"Client heeft een ongeldig mediaverzoek gegenereerd (SDP-fout 2202)", "app.sfu.noAvailableCodec2203": "Server kan geen geschikte codec vinden (fout 2203)", "app.meeting.endNotification.ok.label": "OK", - "app.whiteboard.annotations.poll": "Poll-resultaten zijn gepubliceerd", + "app.whiteboard.annotations.poll": "Peiling resultaten zijn gepubliceerd", + "app.whiteboard.annotations.pollResult": "Peiling resultaten", "app.whiteboard.toolbar.tools": "Gereedschappen", "app.whiteboard.toolbar.tools.hand": "Hand", "app.whiteboard.toolbar.tools.pencil": "Potlood", @@ -607,8 +634,8 @@ "app.whiteboard.toolbar.tools.ellipse": "Ellips", "app.whiteboard.toolbar.tools.line": "Lijn", "app.whiteboard.toolbar.tools.text": "Tekst", - "app.whiteboard.toolbar.thickness": "Tekening dikte", - "app.whiteboard.toolbar.thicknessDisabled": "Tekeningdikte is uitgeschakeld", + "app.whiteboard.toolbar.thickness": "Tekendikte", + "app.whiteboard.toolbar.thicknessDisabled": "Tekendikte is uitgeschakeld", "app.whiteboard.toolbar.color": "Kleuren", "app.whiteboard.toolbar.colorDisabled": "Kleuren is uitgeschakeld", "app.whiteboard.toolbar.color.black": "Zwart", @@ -629,10 +656,10 @@ "app.whiteboard.toolbar.multiUserOff": "Whiteboard voor meerdere gebruikers uitschakelen", "app.whiteboard.toolbar.fontSize": "Lijst met lettertypegroottes", "app.feedback.title": "U bent uitgelogd van de conferentie", - "app.feedback.subtitle": "We horen graag over uw ervaring met BigBlueButton (optioneel)", + "app.feedback.subtitle": "We horen graag over uw ervaringen met BigBlueButton (optioneel)", "app.feedback.textarea": "Hoe kunnen we BigBlueButton beter maken?", "app.feedback.sendFeedback": "Feedback verzenden", - "app.feedback.sendFeedbackDesc": "Stuur een feedback en verlaat de vergadering", + "app.feedback.sendFeedbackDesc": "Stuur feedback en verlaat de vergadering", "app.videoDock.webcamFocusLabel": "Focus", "app.videoDock.webcamFocusDesc": "Focus de geselecteerde webcam", "app.videoDock.webcamUnfocusLabel": "Unfocus", @@ -654,13 +681,13 @@ "app.createBreakoutRoom.returnAudio": "Return audio", "app.createBreakoutRoom.alreadyConnected": "Al in kamer", "app.createBreakoutRoom.confirm": "Maken", - "app.createBreakoutRoom.record": "Record", + "app.createBreakoutRoom.record": "Opnemen", "app.createBreakoutRoom.numberOfRooms": "Aantal kamers", "app.createBreakoutRoom.durationInMinutes": "Duur (minuten)", "app.createBreakoutRoom.randomlyAssign": "Willekeurig toewijzen", "app.createBreakoutRoom.endAllBreakouts": "Alle brainstormruimtes beëindigen", "app.createBreakoutRoom.roomName": "{0} (Kamer - {1})", - "app.createBreakoutRoom.doneLabel": "Done", + "app.createBreakoutRoom.doneLabel": "Gereed", "app.createBreakoutRoom.nextLabel": "Volgende", "app.createBreakoutRoom.minusRoomTime": "Tijd voor brainstormruimte verlagen tot", "app.createBreakoutRoom.addRoomTime": "Tijd voor brainstormruimte verhogen tot", @@ -677,15 +704,15 @@ "app.externalVideo.urlError": "Deze video-URL wordt niet ondersteund", "app.externalVideo.close": "Sluiten", "app.externalVideo.autoPlayWarning": "Speel de video af om mediasynchronisatie in te schakelen", - "app.network.connection.effective.slow": "We merken verbindingsproblemen op.", + "app.network.connection.effective.slow": "We constateren verbindingsproblemen.", "app.network.connection.effective.slow.help": "Meer informatie", - "app.externalVideo.noteLabel": "Opmerking: gedeelde externe video's verschijnen niet in de opname. URL's voor YouTube, Vimeo, Instructure Media, Twitch en Daily Motion worden ondersteund.", + "app.externalVideo.noteLabel": "Opmerking: gedeelde externe video's worden niet weergegeven in de opname. YouTube, Vimeo, Instructure Media, Twitch, Dailymotion en mediabestand-URL's (bijv. https://example.com/xy.mp4) worden ondersteund.", "app.actionsBar.actionsDropdown.shareExternalVideo": "Deel een externe video", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Stop met het delen van externe video", "app.iOSWarning.label": "Upgrade naar iOS 12.2 of hoger", "app.legacy.unsupportedBrowser": "Het lijkt erop dat u een browser gebruikt die niet wordt ondersteund. Gebruik {0} of {1} voor volledige ondersteuning.", "app.legacy.upgradeBrowser": "Het lijkt erop dat u een oudere versie van een ondersteunde browser gebruikt. Upgrade uw browser voor volledige ondersteuning.", - "app.legacy.criosBrowser": "Gebruik op iOS Safari voor volledige ondersteuning." + "app.legacy.criosBrowser": "Gebruik Safari op iOS voor volledige ondersteuning." } diff --git a/bigbluebutton-html5/private/locales/oc.json b/bigbluebutton-html5/private/locales/oc.json new file mode 100644 index 0000000000000000000000000000000000000000..fc40f4ffff85fa48892b75ae7813ecd6314e3fdb --- /dev/null +++ b/bigbluebutton-html5/private/locales/oc.json @@ -0,0 +1,260 @@ +{ + "app.home.greeting": "Vòstra presentacion començarà dins una estona...", + "app.chat.submitLabel": "Enviar messatge", + "app.chat.errorMaxMessageLength": "Lo messatge conten {0} caractèr(s) de tròp", + "app.chat.disconnected": "Sètz desconnectat, impossible d’enviar lo messatge", + "app.chat.locked": "Lo chat es verrolhat, impossible d’enviar lo messatge", + "app.chat.inputPlaceholder": "Enviar messatge a {0}", + "app.chat.titlePublic": "Chat public", + "app.chat.titlePrivate": "Chat privat amb {0}", + "app.chat.partnerDisconnected": "{0} a quitat la conferéncia", + "app.chat.closeChatLabel": "Tampar {0}", + "app.chat.hideChatLabel": "Rescondre {0}", + "app.chat.moreMessages": "Mai de messatge çai-jos", + "app.chat.dropdown.options": "Opcions de chat", + "app.chat.dropdown.clear": "Escafar", + "app.chat.dropdown.copy": "Copiar", + "app.chat.dropdown.save": "Enregistrar", + "app.chat.label": "Chat", + "app.chat.offline": "Fòra linha", + "app.chat.pollResult": "Resultats sondatge", + "app.chat.emptyLogLabel": "Jornal de chat void", + "app.chat.clearPublicChatMessage": "La conversacion publica es estat escafada per un moderator", + "app.chat.multi.typing": "Mantun utilizaires son a picar", + "app.chat.one.typing": "{0} escriu", + "app.chat.two.typing": "{0} e {1} escrivon", + "app.captions.label": "Legendas", + "app.captions.menu.close": "Tampar", + "app.captions.menu.start": "Començar", + "app.captions.menu.ariaStart": "Començar d’escriure las legendas", + "app.captions.menu.select": "Seleccionar una lenga disponibla", + "app.captions.menu.ariaSelect": "Lenga de las legendas", + "app.captions.menu.fontSize": "Talha", + "app.captions.menu.fontColor": "Color del tèxt", + "app.captions.menu.fontFamily": "Poliça", + "app.captions.menu.backgroundColor": "Color de fons", + "app.captions.menu.previewLabel": "Apercebut", + "app.captions.menu.cancelLabel": "Anullar", + "app.captions.pad.dictationStart": "Començar dictada", + "app.captions.pad.dictationStop": "Arrestar dictada", + "app.captions.pad.dictationOnDesc": "Activar reconeissença de la votz", + "app.captions.pad.dictationOffDesc": "Desactivar reconeissença de la votz", + "app.note.title": "Nòtas partejadas", + "app.note.label": "Nòta", + "app.note.hideNoteLabel": "Amagar nòta", + "app.user.activityCheck.check": "Verificar", + "app.userList.usersTitle": "Utilizaires", + "app.userList.participantsTitle": "Participants", + "app.userList.messagesTitle": "Messatges", + "app.userList.notesTitle": "Nòtas", + "app.userList.captionsTitle": "Legendas", + "app.userList.presenter": "Presentator", + "app.userList.you": "Vos", + "app.userList.locked": "Verrolhat", + "app.userList.label": "Lista utilizaires", + "app.userList.guest": "Convidat", + "app.userList.menuTitleContext": "Opcions disponiblas", + "app.userList.chatListItem.unreadSingular": "{0} Messatge novèl", + "app.userList.chatListItem.unreadPlural": "{0} Messatges novèls", + "app.userList.menu.chat.label": "Començar una discussion privada", + "app.userList.menu.clearStatus.label": "Escafar estatut", + "app.userList.menu.removeUser.label": "Levar l’utilizaire", + "app.userList.menu.removeConfirmation.label": "Tirar l’utilizaire ({0})", + "app.userList.menu.muteUserAudio.label": "Silenciar l’utilizaire", + "app.userList.menu.unmuteUserAudio.label": "Tornar la paraula a l’utilizaire", + "app.userList.menu.unlockUser.label": "Desbarrar {0}", + "app.userList.menu.lockUser.label": "Barrar {0}", + "app.userList.menu.makePresenter.label": "Far venir presentator", + "app.userList.userOptions.manageUsersLabel": "Gerir los utilizaires", + "app.userList.userOptions.connectionStatusLabel": "Estat connexion", + "app.userList.userOptions.connectionStatusDesc": "Veire l’estat de la connexion de l’utilizaire", + "app.userList.userOptions.disablePrivChat": "Lo chat privat es desactivat", + "app.userList.userOptions.disablePubChat": "Lo chat public es desactivat", + "app.media.label": "Mèdia", + "app.media.autoplayAlertDesc": "Autorizar l’accès", + "app.media.screenshare.start": "Lo partatge d’ecran a començat", + "app.media.screenshare.end": "Lo partatge d’ecran es acabat", + "app.media.screenshare.unavailable": "Partatge d’ecran indisponible", + "app.meeting.ended": "Aquesta ession es acabada", + "app.presentation.hide": "Amagar la presentacion", + "app.presentation.notificationLabel": "Presentacion actuala", + "app.presentation.presentationToolbar.noNextSlideDesc": "Terminar la presentacion", + "app.presentation.presentationToolbar.noPrevSlideDesc": "Lançar la presentacion", + "app.presentation.presentationToolbar.fitWidthLabel": "Adaptar a la largor", + "app.presentation.presentationToolbar.fitScreenLabel": "Adaptar a l’ecran", + "app.presentation.presentationToolbar.zoomInLabel": "Agrandir", + "app.presentation.presentationToolbar.zoomOutLabel": "Reduire", + "app.presentation.presentationToolbar.zoomReset": "Reïnicializar lo zoom", + "app.presentation.presentationToolbar.fitToWidth": "Adaptar a la largor", + "app.presentation.presentationToolbar.fitToPage": "Adaptar a la pagina", + "app.presentationUploder.title": "Presentacion", + "app.presentationUploder.uploadLabel": "Mandar", + "app.presentationUploder.confirmLabel": "Confirmar", + "app.presentationUploder.dismissLabel": "Anullar", + "app.presentationUploder.upload.progress": "MandadÃs ({0}%)", + "app.presentationUploder.conversion.genericConversionStatus": "Conversion del fichièr...", + "app.presentationUploder.conversion.generatingThumbnail": "Generacion de la miniatura...", + "app.presentationUploder.conversion.generatingSvg": "Generacion dels imatges SVG...", + "app.presentationUploder.tableHeading.filename": "Nom de fichièr", + "app.presentationUploder.tableHeading.options": "Opcions", + "app.presentationUploder.tableHeading.status": "Estatut", + "app.poll.quickPollTitle": "Sondatge rapid", + "app.poll.closeLabel": "Tampar", + "app.poll.t": "Verai", + "app.poll.f": "Fals", + "app.poll.tf": "Verai / Fals", + "app.poll.y": "Ã’c", + "app.poll.n": "Non", + "app.poll.yn": "Ã’c / Non", + "app.poll.a2": "A / B", + "app.poll.a3": "A / B / C", + "app.poll.a4": "A / B / C / D", + "app.poll.a5": "A / B / C / D / E", + "app.poll.answer.true": "Verai", + "app.poll.answer.false": "Fals", + "app.poll.answer.yes": "Ã’c", + "app.poll.answer.no": "Non", + "app.poll.answer.a": "A", + "app.poll.answer.b": "B", + "app.poll.answer.c": "C", + "app.poll.answer.d": "D", + "app.poll.answer.e": "E", + "app.poll.liveResult.usersTitle": "Utilizaires", + "app.poll.liveResult.responsesTitle": "Responsa", + "app.polling.pollingTitle": "Opcions del sondatge", + "app.connectingMessage": "Connexion...", + "app.retryNow": "Tornar ensajar ara", + "app.navBar.settingsDropdown.optionsLabel": "Opcions", + "app.navBar.settingsDropdown.fullscreenLabel": "Passar al plen ecran", + "app.navBar.settingsDropdown.settingsLabel": "Paramètres", + "app.navBar.settingsDropdown.aboutLabel": "A prepaus", + "app.navBar.settingsDropdown.leaveSessionLabel": "Desconnexion", + "app.navBar.settingsDropdown.exitFullscreenLabel": "Quitar lo plen ecran", + "app.navBar.settingsDropdown.hotkeysLabel": "Acorchis clavièr", + "app.navBar.settingsDropdown.helpLabel": "Ajuda", + "app.navBar.settingsDropdown.endMeetingLabel": "Terminar la reünion", + "app.navBar.recording.on": "Enregistrament", + "app.leaveConfirmation.confirmLabel": "Quitar", + "app.endMeeting.title": "Terminar la reünion", + "app.endMeeting.yesLabel": "Ã’c", + "app.endMeeting.noLabel": "Non", + "app.about.title": "A prepaus", + "app.about.confirmLabel": "D’acòrdi", + "app.about.confirmDesc": "D’acòrdi", + "app.about.dismissLabel": "Anullar", + "app.actionsBar.actionsDropdown.restorePresentationLabel": "Restaurar la presentacion", + "app.screenshare.screenShareLabel" : "Partatge d’ecran", + "app.submenu.application.applicationSectionTitle": "Aplicacion", + "app.submenu.application.animationsLabel": "Animacions", + "app.submenu.application.fontSizeControlLabel": "Talha de poliça", + "app.submenu.application.languageLabel": "Lenga de l’aplicacion", + "app.submenu.application.languageOptionLabel": "Causir la lenga", + "app.submenu.application.noLocaleOptionLabel": "Cap de lenga pas disponibla", + "app.submenu.audio.micSourceLabel": "Font microfòn", + "app.submenu.audio.speakerSourceLabel": "Font naut-parlaire", + "app.submenu.video.title": "Vidèo", + "app.submenu.video.videoSourceLabel": "Veire la font", + "app.submenu.video.videoQualityLabel": "Qualitat vidèo", + "app.submenu.video.qualityOptionLabel": "Causissètz la qualitat vidèo", + "app.submenu.video.participantsCamLabel": "Visualizacion de la camèra dels participants", + "app.settings.applicationTab.label": "Aplicacion", + "app.settings.audioTab.label": "Àudio", + "app.settings.videoTab.label": "Vidèo", + "app.settings.usersTab.label": "Participants", + "app.settings.main.label": "Paramètres", + "app.settings.main.cancel.label": "Anullar", + "app.settings.main.save.label": "Enregistrar", + "app.settings.dataSavingTab.label": "Estalvi de donadas", + "app.settings.dataSavingTab.webcam": "Activar las camèras", + "app.settings.dataSavingTab.screenShare": "Activar lo partatge d’ecran", + "app.talkingIndicator.isTalking" : "{0} parla", + "app.actionsBar.actionsDropdown.actionsLabel": "Accions", + "app.actionsBar.actionsDropdown.initPollLabel": "Aviar un sondatge", + "app.actionsBar.actionsDropdown.desktopShareLabel": "Partejar vòstre ecran", + "app.actionsBar.actionsDropdown.lockedDesktopShareLabel": "Partatge d’ecran blocat", + "app.actionsBar.actionsDropdown.initPollDesc": "Aviar un sondatge", + "app.actionsBar.actionsDropdown.pollBtnLabel": "Aviar un sondatge", + "app.actionsBar.emojiMenu.awayLabel": "Absent", + "app.actionsBar.emojiMenu.sadLabel": "Trist", + "app.actionsBar.emojiMenu.happyLabel": "Urós", + "app.audioNotification.closeLabel": "Tampar", + "app.breakoutJoinConfirmation.dismissLabel": "Anullar", + "app.audioModal.microphoneLabel": "Microfòn", + "app.audioModal.listenOnlyLabel": "Sonque escotar", + "app.audioModal.closeLabel": "Tampar", + "app.audioModal.yes": "Ã’c", + "app.audioModal.no": "Non", + "app.audioDial.tipIndicator": "Astúcia", + "app.audioModal.connecting": "Connexion", + "app.audio.backLabel": "Tornar", + "app.audio.audioSettings.microphoneSourceLabel": "Font microfòn", + "app.audio.audioSettings.speakerSourceLabel": "Font naut-parlaire", + "app.audio.audioSettings.retryLabel": "Tornar ensajar", + "app.audio.listenOnly.backLabel": "Tornar", + "app.audio.listenOnly.closeLabel": "Tampar", + "app.audio.permissionsOverlay.title": "Autorizatz l’accès al microfòn", + "app.modal.close": "Tampar", + "app.dropdown.close": "Tampar", + "app.error.400": "Marrida requèsta", + "app.error.401": "Pas autorizada", + "app.error.fallback.presentation.reloadButton": "Recargar", + "app.toast.chat.system": "Sistèma", + "app.shortcut-help.title": "Acorchis clavièr", + "app.shortcut-help.functionLabel": "Foncion", + "app.shortcut-help.closeLabel": "Tampar", + "app.shortcut-help.openOptions": "Dobrir las opcions", + "app.lock-viewers.featuresLable": "Foncion", + "app.lock-viewers.lockStatusLabel": "Estatut", + "app.lock-viewers.webcamLabel": "Partejar la camèra", + "app.lock-viewers.microphoneLable": "Partejar lo microfòn", + "app.lock-viewers.button.apply": "Aplicar", + "app.lock-viewers.button.cancel": "Anullar", + "app.lock-viewers.locked": "Verrolhat", + "app.videoPreview.cameraLabel": "Camèra", + "app.videoPreview.profileLabel": "Qualitat", + "app.videoPreview.cancelLabel": "Anullar", + "app.videoPreview.closeLabel": "Tampar", + "app.videoPreview.webcamOptionLabel": "Causissètz la camèra", + "app.videoPreview.webcamPreviewLabel": "Apercebut camèra", + "app.videoPreview.webcamSettingsTitle": "Paramètres de camèra", + "app.videoPreview.webcamNotFoundLabel": "Camèra pas trobada", + "app.videoPreview.profileNotFoundLabel": "Perfil de camèra pas compatibla", + "app.video.joinVideo": "Partejar la camèra", + "app.video.leaveVideo": "Arrestar lo partatge camèra", + "app.video.enable": "Activar", + "app.video.cancel": "Anullar", + "app.video.swapCam": "Escambiar", + "app.video.videoButtonDesc": "Partejar la camèra", + "app.video.videoMenu": "Menú vidèo", + "app.video.chromeExtensionError": "Vos cal installar", + "app.video.chromeExtensionErrorLink": "aqueste extension Chrome", + "app.meeting.endNotification.ok.label": "D’acòrdi", + "app.whiteboard.toolbar.tools": "Aisinas", + "app.whiteboard.toolbar.tools.text": "Tèxt", + "app.whiteboard.toolbar.color": "Colors", + "app.whiteboard.toolbar.color.black": "Negre", + "app.whiteboard.toolbar.color.white": "Blanc", + "app.whiteboard.toolbar.color.red": "Roge", + "app.whiteboard.toolbar.color.orange": "Irange", + "app.whiteboard.toolbar.color.eletricLime": "Verd electric", + "app.whiteboard.toolbar.color.lime": "Limon", + "app.whiteboard.toolbar.color.cyan": "Cian", + "app.whiteboard.toolbar.color.blue": "Blau", + "app.whiteboard.toolbar.color.violet": "Violet", + "app.createBreakoutRoom.duration": "Durada {0}", + "app.createBreakoutRoom.room": "Sala {0}", + "app.createBreakoutRoom.confirm": "Crear", + "app.createBreakoutRoom.record": "Enregistrar", + "app.createBreakoutRoom.numberOfRooms": "Nombre de salas", + "app.createBreakoutRoom.durationInMinutes": "Durada (minutas)", + "app.createBreakoutRoom.nextLabel": "Seguent", + "app.createBreakoutRoom.addParticipantLabel": "+ Ajustar participant", + "app.createBreakoutRoom.roomTime": "{0} minutas", + "app.createBreakoutRoom.numberOfRoomsError": "Lo nombre de salas es invalid.", + "app.externalVideo.start": "Partejar una vidèo novèla", + "app.externalVideo.urlInput": "Ajustar l’URL d’una vidèo", + "app.externalVideo.close": "Tampar", + "app.network.connection.effective.slow.help": "Mai d‘informacions" + +} + diff --git a/bigbluebutton-html5/private/locales/pl_PL.json b/bigbluebutton-html5/private/locales/pl_PL.json index ce7bd707b20922dc45e90d5436c71a6a247f8156..5f2edb17fee517715f6f463a66fa639328f06158 100644 --- a/bigbluebutton-html5/private/locales/pl_PL.json +++ b/bigbluebutton-html5/private/locales/pl_PL.json @@ -18,6 +18,7 @@ "app.chat.dropdown.save": "Zapisz", "app.chat.label": "Czat", "app.chat.offline": "Offline", + "app.chat.pollResult": "Wyniki ankiety", "app.chat.emptyLogLabel": "Dziennik czatu pusty", "app.chat.clearPublicChatMessage": "Historia Czatu Publicznego wyczyszczona przez moderatora", "app.chat.multi.typing": "Kilku uczestników teraz pisze", @@ -50,10 +51,10 @@ "app.note.title": "Wspólne notatki", "app.note.label": "Notatka", "app.note.hideNoteLabel": "Ukryj notatkÄ™", + "app.note.tipLabel": "NaciÅ›nij Esc by aktywować pasek narzÄ™dziowy edytora", "app.user.activityCheck": "Sprawdź aktywność uczestnika", "app.user.activityCheck.label": "Sprawdź czy uczestnik nadal uczestniczy w spotkaniu ({0})", "app.user.activityCheck.check": "Sprawdź", - "app.note.tipLabel": "NaciÅ›nij Esc by aktywować pasek narzÄ™dziowy edytora", "app.userList.usersTitle": "Uczestnicy", "app.userList.participantsTitle": "Uczestnicy", "app.userList.messagesTitle": "WiadomoÅ›ci", @@ -122,8 +123,6 @@ "app.meeting.meetingTimeRemaining": "Czas do koÅ„ca spotkania: {0}", "app.meeting.meetingTimeHasEnded": "Koniec czasu. Spotkanie wkrótce siÄ™ zakoÅ„czy", "app.meeting.endedMessage": "Zostaniesz przekierowany do strony domowej", - "app.meeting.alertMeetingEndsUnderOneMinute": "Spotkanie skoÅ„czy siÄ™ za minutÄ™", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Praca w podgrupie zakoÅ„czy siÄ™ za minutÄ™", "app.presentation.hide": "Ukryj prezentacjÄ™", "app.presentation.notificationLabel": "Bieżąca prezentacja", "app.presentation.slideContent": "Zawartość slajdu", @@ -260,7 +259,6 @@ "app.leaveConfirmation.confirmLabel": "Wyjdź", "app.leaveConfirmation.confirmDesc": "Wylogowuje CiÄ™ ze spotkania", "app.endMeeting.title": "ZakoÅ„cz spotkanie", - "app.endMeeting.description": "Czy chcesz zakoÅ„czyć tÄ™ sesjÄ™?", "app.endMeeting.yesLabel": "Tak", "app.endMeeting.noLabel": "Nie", "app.about.title": "O kliencie", @@ -281,10 +279,6 @@ "app.screenshare.screenShareLabel" : "UdostÄ™pnianie ekranu", "app.submenu.application.applicationSectionTitle": "Aplikacja", "app.submenu.application.animationsLabel": "Animacje", - "app.submenu.application.audioAlertLabel": "DźwiÄ™ki powiadomieÅ„ dla czatu", - "app.submenu.application.pushAlertLabel": "Powiadomienia dla czatu", - "app.submenu.application.userJoinAudioAlertLabel": "Powiadomienie dźwiÄ™kowe o dołączeniu użytkownika", - "app.submenu.application.userJoinPushAlertLabel": "Powiadomienie o dołączeniu użytkownika", "app.submenu.application.fontSizeControlLabel": "Rozmiar czcionki", "app.submenu.application.increaseFontBtnLabel": "ZwiÄ™ksz rozmiar czcionki", "app.submenu.application.decreaseFontBtnLabel": "Zmniejsz rozmiar czcionki", @@ -566,19 +560,6 @@ "app.video.videoMenuDesc": "Otwiera menu wideo", "app.video.chromeExtensionError": "Musisz zainstalować", "app.video.chromeExtensionErrorLink": "to rozszerzenie Chrome", - "app.video.stats.title": "Statystyki połączenia", - "app.video.stats.packetsReceived": "Odebrane pakiety", - "app.video.stats.packetsSent": "WysÅ‚ane pakiety", - "app.video.stats.packetsLost": "Utracone pakiety", - "app.video.stats.bitrate": "Bitrate", - "app.video.stats.lostPercentage": "Procent utraconych pakietów", - "app.video.stats.lostRecentPercentage": "Procent ostatnio utraconych pakietów", - "app.video.stats.dimensions": "Rozmiar", - "app.video.stats.codec": "Kodek", - "app.video.stats.decodeDelay": "Opóźnienie dekodowania", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Wskaźnik użycia kodowania", - "app.video.stats.currentDelay": "Bieżące opóźnienie", "app.fullscreenButton.label": "Przełącz {0} na peÅ‚ny ekran", "app.deskshare.iceConnectionStateError": "Błąd połączenia podczas współdzielenia ekranu (ICE 1108)", "app.sfu.mediaServerConnectionError2000": "Nie można połączyć siÄ™ z serwerem multimediów (błąd 2000)", @@ -591,7 +572,6 @@ "app.sfu.invalidSdp2202":"Klient utworzyÅ‚ nieprawidÅ‚owe żądanie multimediów (SDP 2202)", "app.sfu.noAvailableCodec2203": "Serwer nie odnalazÅ‚ odpowiedniego kodeka (błąd 2203)", "app.meeting.endNotification.ok.label": "OK", - "app.whiteboard.annotations.poll": "Opublikowano wyniki ankiety", "app.whiteboard.toolbar.tools": "NarzÄ™dzia", "app.whiteboard.toolbar.tools.hand": "RÄ…czka", "app.whiteboard.toolbar.tools.pencil": "Ołówek", @@ -672,7 +652,6 @@ "app.externalVideo.autoPlayWarning": "Odtwórz wideo, aby włączyć synchronizacjÄ™ multimediów", "app.network.connection.effective.slow": "ZaobserwowaliÅ›my problemy z łącznoÅ›ciÄ….", "app.network.connection.effective.slow.help": "WiÄ™cej informacji", - "app.externalVideo.noteLabel": "Uwaga: UdostÄ™pione zewnÄ™trzne filmy nie sÄ… widoczne na nagraniu. ObsÅ‚ugiwane sÄ… adresy YouTube, Vimeo, Instructure Media, Twitch and Daily Motion.", "app.actionsBar.actionsDropdown.shareExternalVideo": "UdostÄ™pnij zewnÄ™trzny film", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "ZakoÅ„cz udostÄ™pnianie filmu", "app.iOSWarning.label": "Zaktualizuj do wersji iOS 12.2 lub wyższej", diff --git a/bigbluebutton-html5/private/locales/pt.json b/bigbluebutton-html5/private/locales/pt.json index ccb57fb8436806a6bb952e1ae9ef947b2cf97a0e..fcbbc161036e3f0cc978bd1c1c129b313e76c853 100644 --- a/bigbluebutton-html5/private/locales/pt.json +++ b/bigbluebutton-html5/private/locales/pt.json @@ -50,10 +50,10 @@ "app.note.title": "Notas Partilhadas", "app.note.label": "Nota", "app.note.hideNoteLabel": "Ocultar nota", + "app.note.tipLabel": "Prima Esc para barra voltar à barra de ferramentas do editor", "app.user.activityCheck": "Verificar atividade do utilizador", "app.user.activityCheck.label": "Verificar se o utilizador ainda está na sessão ({0})", "app.user.activityCheck.check": "Verificar", - "app.note.tipLabel": "Prima Esc para barra voltar à barra de ferramentas do editor", "app.userList.usersTitle": "Utilizadores", "app.userList.participantsTitle": "Participantes", "app.userList.messagesTitle": "Mensagens", @@ -126,8 +126,6 @@ "app.meeting.meetingTimeRemaining": "Tempo restante da sessão: {0}", "app.meeting.meetingTimeHasEnded": "Tempo limite atingido. A sessão vai fechar dentro de momentos", "app.meeting.endedMessage": "Vai ser redirecionado para o ecrã inicial", - "app.meeting.alertMeetingEndsUnderOneMinute": "Sessão vai terminar dentro de 1 minuto", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Sala de grupo vai terminar dentro de 1 minuto", "app.presentation.hide": "Ocultar a apresentação", "app.presentation.notificationLabel": "Apresentação atual", "app.presentation.slideContent": "Conteúdo de slide", @@ -171,7 +169,7 @@ "app.presentationUploder.browseImagesLabel": "ou procure/capture imagens", "app.presentationUploder.fileToUpload": "Para carregar ...", "app.presentationUploder.currentBadge": "Atual", - "app.presentationUploder.rejectedError": "O(s) ficheiro(s) selecionados foram rejeitados.\nVerifique por favor os tipos de ficheiro.", + "app.presentationUploder.rejectedError": "O(s) ficheiro(s) selecionados foram rejeitados. Verifique por favor os tipos de ficheiro.", "app.presentationUploder.upload.progress": "A carregar ({0}%)", "app.presentationUploder.upload.413": "O ficheiro é demasiado grande. Por favor divida o mesmo em vários ficheiros.", "app.presentationUploder.upload.408": "Timeout no pedido de token de carregamento.", @@ -202,7 +200,7 @@ "app.poll.quickPollInstruction": "Selecione uma opção abaixo para iniciar a sua sondagem.", "app.poll.customPollLabel": "Sondagem personalizada", "app.poll.startCustomLabel": "Iniciar sondagem personalizada", - "app.poll.activePollInstruction": "Deixe este painel aberto de forma a ver as respostas à sua sondagem, em tempo real. Quando estiver pronto, escolha \"Publicar resultado da sondagem\" para publicar os resultados e fechar a sondagem.", + "app.poll.activePollInstruction": "Deixe este painel aberto de forma a ver as respostas à sua sondagem, em tempo real. Quando estiver pronto, escolha 'Publicar resultado da sondagem' para publicar os resultados e fechar a sondagem.", "app.poll.publishLabel": "Publicar resultados de sondagem", "app.poll.backLabel": "Voltar à s opções da sondagem", "app.poll.closeLabel": "Fechar", @@ -238,7 +236,7 @@ "app.failedMessage": "Lamentamos mas estamos com problemas de ligação ao servidor", "app.downloadPresentationButton.label": "Descarregar a apresentação original", "app.connectingMessage": "A ligar ...", - "app.waitingMessage": "Desligado. A tentar ligar novamente em {0} segundos ...\n", + "app.waitingMessage": "Desligado. A tentar ligar novamente em {0} segundos ...", "app.retryNow": "Tentar agora", "app.navBar.settingsDropdown.optionsLabel": "Opções", "app.navBar.settingsDropdown.fullscreenLabel": "Alternar para ecrã inteiro", @@ -267,7 +265,6 @@ "app.leaveConfirmation.confirmLabel": "Sair", "app.leaveConfirmation.confirmDesc": "Sai da sessão", "app.endMeeting.title": "Terminar sessão", - "app.endMeeting.description": "Tem certeza que pretende terminar a sessão?", "app.endMeeting.yesLabel": "Sim", "app.endMeeting.noLabel": "Não", "app.about.title": "Sobre", @@ -288,10 +285,6 @@ "app.screenshare.screenShareLabel" : "Partilhar ecrã", "app.submenu.application.applicationSectionTitle": "Aplicação", "app.submenu.application.animationsLabel": "Animações", - "app.submenu.application.audioAlertLabel": "Alertas de áudio do chat", - "app.submenu.application.pushAlertLabel": "Alertas pop-up do chat", - "app.submenu.application.userJoinAudioAlertLabel": "Alerta audio quando um utilizador entrar", - "app.submenu.application.userJoinPushAlertLabel": "Alerta popup quando um utilizador entrar", "app.submenu.application.fontSizeControlLabel": "Tamanho da fonte", "app.submenu.application.increaseFontBtnLabel": "Aumentar o tamanho da fonte da aplicação", "app.submenu.application.decreaseFontBtnLabel": "Diminuir o tamanho da fonte da aplicação", @@ -573,19 +566,6 @@ "app.video.videoMenuDesc": "Abra o menu de vÃdeo", "app.video.chromeExtensionError": "Deve instalar o seguinte:", "app.video.chromeExtensionErrorLink": "esta extensão Chrome", - "app.video.stats.title": "EstatÃsticas da ligação", - "app.video.stats.packetsReceived": "Pacotes recebidos", - "app.video.stats.packetsSent": "Pacotes enviados", - "app.video.stats.packetsLost": "Pacotes perdidos", - "app.video.stats.bitrate": "Bitrate", - "app.video.stats.lostPercentage": "Percentual de perda total", - "app.video.stats.lostRecentPercentage": "Percentual de perda recentemente", - "app.video.stats.dimensions": "Dimensões", - "app.video.stats.codec": "Codec", - "app.video.stats.decodeDelay": "Atraso de descodificação", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Uso de codificação", - "app.video.stats.currentDelay": "Atraso atual", "app.fullscreenButton.label": "Passar {0} a ecrã inteiro", "app.deskshare.iceConnectionStateError": "A ligação falhou ao partilhar o ecrã (erro ICE 1108)", "app.sfu.mediaServerConnectionError2000": "Não foi possÃvel ligar ao media server (erro 2000)", @@ -598,7 +578,6 @@ "app.sfu.invalidSdp2202":"O cliente gerou um pedido inválido de media (erro SDP 2202)", "app.sfu.noAvailableCodec2203": "O servidor não foi capaz de encontrar o codec apropriado (erro 2203)", "app.meeting.endNotification.ok.label": "OK", - "app.whiteboard.annotations.poll": "Os resultados da sondagem foram publicados", "app.whiteboard.toolbar.tools": "Ferramentas", "app.whiteboard.toolbar.tools.hand": "Pan", "app.whiteboard.toolbar.tools.pencil": "Lápis", @@ -679,7 +658,6 @@ "app.externalVideo.autoPlayWarning": "Reproduzir o vÃdeo de forma a activar a sincronização de media", "app.network.connection.effective.slow": "Estão a ocorrer problemas na ligação", "app.network.connection.effective.slow.help": "Mais informações", - "app.externalVideo.noteLabel": "Nota: vÃdeos partilhados através de links externos não serão exibidos em gravações de sala. Youtube, Vimeo, Instructure Media, Twitch e Daily Motion são tipos de URL de vÃdeos externos suportados.", "app.actionsBar.actionsDropdown.shareExternalVideo": "Partilhar um vÃdeo externo", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Parar a partilha de vÃdeo externo", "app.iOSWarning.label": "Por favor atualize para iOS 12.2 ou posterior", diff --git a/bigbluebutton-html5/private/locales/pt_BR.json b/bigbluebutton-html5/private/locales/pt_BR.json index eae53e7a252a1c6babdf1053012c3ad0b92b38d3..19b9e325a4eeca12e7694da8fed6550a0eeba362 100644 --- a/bigbluebutton-html5/private/locales/pt_BR.json +++ b/bigbluebutton-html5/private/locales/pt_BR.json @@ -50,10 +50,10 @@ "app.note.title": "Notas compartilhadas", "app.note.label": "Nota", "app.note.hideNoteLabel": "Ocultar nota", + "app.note.tipLabel": "Pressione Esc para focar na barra de ferramentas do editor", "app.user.activityCheck": "Verificação de atividade do usuário", "app.user.activityCheck.label": "Verifica se o usuário ainda está na sala ({0})", "app.user.activityCheck.check": "Verificar", - "app.note.tipLabel": "Pressione Esc para focar na barra de ferramentas do editor", "app.userList.usersTitle": "Usuários", "app.userList.participantsTitle": "Participantes", "app.userList.messagesTitle": "Mensagens", @@ -126,8 +126,6 @@ "app.meeting.meetingTimeRemaining": "Tempo restante da sessão: {0}", "app.meeting.meetingTimeHasEnded": "Tempo esgotado. A sessão será fechada em breve", "app.meeting.endedMessage": "Você será redirecionado para a tela inicial", - "app.meeting.alertMeetingEndsUnderOneMinute": "Sessão será fechada em um minuto.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Sala de apoio será fechada em um minuto.", "app.presentation.hide": "Minimizar apresentação", "app.presentation.notificationLabel": "Apresentação atual", "app.presentation.slideContent": "Conteúdo do slide", @@ -267,7 +265,6 @@ "app.leaveConfirmation.confirmLabel": "Sair", "app.leaveConfirmation.confirmDesc": "Desconecta da sessão", "app.endMeeting.title": "Encerrar sessão", - "app.endMeeting.description": "Você tem certeza que deseja encerrar a sessão?", "app.endMeeting.yesLabel": "Sim", "app.endMeeting.noLabel": "Não", "app.about.title": "Sobre", @@ -288,10 +285,6 @@ "app.screenshare.screenShareLabel" : "Compartilhamento de tela", "app.submenu.application.applicationSectionTitle": "Aplicação", "app.submenu.application.animationsLabel": "Animações", - "app.submenu.application.audioAlertLabel": "Alertas de áudio para bate-papo", - "app.submenu.application.pushAlertLabel": "Alertas de pop-up para bate-papo", - "app.submenu.application.userJoinAudioAlertLabel": "Alertas de áudio quando novos participantes entram na sala", - "app.submenu.application.userJoinPushAlertLabel": "Alertas de pop-up quando novos participantes entram na sala", "app.submenu.application.fontSizeControlLabel": "Tamanho da fonte", "app.submenu.application.increaseFontBtnLabel": "Aumentar o tamanho da fonte da aplicação", "app.submenu.application.decreaseFontBtnLabel": "Diminuir o tamanho da fonte da aplicação", @@ -573,19 +566,6 @@ "app.video.videoMenuDesc": "Abra o menu de vÃdeo", "app.video.chromeExtensionError": "Você deve instalar o seguinte:", "app.video.chromeExtensionErrorLink": "esta extensão do Chrome", - "app.video.stats.title": "EstatÃsticas de conexão", - "app.video.stats.packetsReceived": "Pacotes recebidos", - "app.video.stats.packetsSent": "Pacotes enviados", - "app.video.stats.packetsLost": "Pacotes perdidos", - "app.video.stats.bitrate": "Taxa de bits", - "app.video.stats.lostPercentage": "Percentuais de perda total", - "app.video.stats.lostRecentPercentage": "Percentuais de perda recentemente", - "app.video.stats.dimensions": "Dimensões", - "app.video.stats.codec": "Codec", - "app.video.stats.decodeDelay": "Atraso de decodificação", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Uso de codificação", - "app.video.stats.currentDelay": "Atraso atual", "app.fullscreenButton.label": "Alternar {0} para tela cheia", "app.deskshare.iceConnectionStateError": "Falha na conexão ao compartilhar a tela (erro ICE 1108)", "app.sfu.mediaServerConnectionError2000": "Não foi possÃvel conectar ao servidor de mÃdia (erro 2000)", @@ -598,7 +578,6 @@ "app.sfu.invalidSdp2202":"O cliente gerou uma solicitação de mÃdia inválida (erro SDP 2202)", "app.sfu.noAvailableCodec2203": "O servidor não conseguiu encontrar um codec apropriado (erro 2203)", "app.meeting.endNotification.ok.label": "OK", - "app.whiteboard.annotations.poll": "Os resultados da enquete foram publicados", "app.whiteboard.toolbar.tools": "Ferramentas", "app.whiteboard.toolbar.tools.hand": "Mover", "app.whiteboard.toolbar.tools.pencil": "Lápis", @@ -679,7 +658,6 @@ "app.externalVideo.autoPlayWarning": "Clique no vÃdeo para habilitar a sincronização", "app.network.connection.effective.slow": "Estamos percebendo problemas de conectividade.", "app.network.connection.effective.slow.help": "Mais informações", - "app.externalVideo.noteLabel": "Nota: vÃdeos externos compartilhados não aprecerão na gravação. Videos de YouTube, Vimeo, Instructure Media, Twitch e Daily Motion são suportados.", "app.actionsBar.actionsDropdown.shareExternalVideo": "Compartilhar um vÃdeo externo", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Parar de compartilhar vÃdeo externo", "app.iOSWarning.label": "Por favor, atualize para o iOS 12.2 ou superior", diff --git a/bigbluebutton-html5/private/locales/ro_RO.json b/bigbluebutton-html5/private/locales/ro_RO.json index a4fa8413262604c642c32e7789e0287d9911355f..228e1bf9c903c8b2a72b2614eaeda3409c516067 100644 --- a/bigbluebutton-html5/private/locales/ro_RO.json +++ b/bigbluebutton-html5/private/locales/ro_RO.json @@ -1,6 +1,6 @@ { "app.home.greeting": "Prezentarea dvs va incepe in curand ...", - "app.chat.submitLabel": "Trimit mesaj", + "app.chat.submitLabel": "Trimite mesaj", "app.chat.errorMaxMessageLength": "Mesajul este cu {0} caracter(e) prea lung", "app.chat.disconnected": "Esti deconectat, nu poti trimite mesaje", "app.chat.locked": "Discutia este blocata, nu poti trimite mesaje", @@ -50,10 +50,10 @@ "app.note.title": "Notite partajate", "app.note.label": "Notite", "app.note.hideNoteLabel": "Ascunde notita", + "app.note.tipLabel": "Apasa Esc pentru editare bara de instrumente", "app.user.activityCheck": "Verificare activitate utilizator", "app.user.activityCheck.label": "Verific daca utilizatorul este inca in sedinta ({0})", "app.user.activityCheck.check": "Verific", - "app.note.tipLabel": "Apasa Esc pentru editare bara de instrumente", "app.userList.usersTitle": "Utilizatori", "app.userList.participantsTitle": "Participanti", "app.userList.messagesTitle": "Mesaje", @@ -121,8 +121,6 @@ "app.meeting.meetingTimeRemaining": "Timp ramas din sedinta: {0}", "app.meeting.meetingTimeHasEnded": "Timpul s-a sfarsit. Sedinta se va incheia in scurt timp", "app.meeting.endedMessage": "Veti fi redirectionat in ecranul de pornire", - "app.meeting.alertMeetingEndsUnderOneMinute": "Sedinta se va incheia intr-un minut.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Breakout-ul se inchide intr-un minut.", "app.presentation.hide": "Ascunde prezentarea ", "app.presentation.notificationLabel": "Prezentarea curenta", "app.presentation.slideContent": "Glisare continut", @@ -255,7 +253,6 @@ "app.leaveConfirmation.confirmLabel": "Paraseste", "app.leaveConfirmation.confirmDesc": "Scoatere din sedinta", "app.endMeeting.title": "Sfarsit sedinta", - "app.endMeeting.description": "Esti sigur ca vrei sa parasesti sedinta?", "app.endMeeting.yesLabel": "Da", "app.endMeeting.noLabel": "Nu", "app.about.title": "Despre", @@ -276,10 +273,6 @@ "app.screenshare.screenShareLabel" : "Partajare ecran", "app.submenu.application.applicationSectionTitle": "Aplicatie", "app.submenu.application.animationsLabel": "Animatii", - "app.submenu.application.audioAlertLabel": "Alerte audio pentru discutie", - "app.submenu.application.pushAlertLabel": "Alerte pop-up pentru discutie", - "app.submenu.application.userJoinAudioAlertLabel": "Alerta audio pentru intrare user", - "app.submenu.application.userJoinPushAlertLabel": "Alerta popup pentru intrare user", "app.submenu.application.fontSizeControlLabel": "Marime font", "app.submenu.application.increaseFontBtnLabel": "Mareste marimea fontului din aplicatie", "app.submenu.application.decreaseFontBtnLabel": "Micsoreaza marimea fontului din aplicatie", @@ -529,7 +522,7 @@ "app.video.leaveVideo": "Opreste partajarea camerei web", "app.video.iceCandidateError": "Eroare la adaugarea unui candidat ICE", "app.video.permissionError": "Eroare la partajarea camerei web. Va rog sa verificati permisiunile", - "app.video.sharingError": "Eroare la partajarea camrei web", + "app.video.sharingError": "Eroare la partajarea camerei web", "app.video.notFoundError": "Nu pot gasi camera web.Va rog asigurati-va ca este conectata", "app.video.notAllowed": "Lipsesc permisiunile pentru partajarea camerei web, va rog asigurati-va de permisiunile din browser", "app.video.notSupportedError": "Se poate partaja camera web doar daca certificatul SSL este valid", @@ -547,22 +540,8 @@ "app.video.videoMenuDesc": "Deschideti dropdown-ul de meniu Video", "app.video.chromeExtensionError": "Trebuie sa instalezi", "app.video.chromeExtensionErrorLink": "acesta extensie de Chrome", - "app.video.stats.title": "Statistici de Conectare", - "app.video.stats.packetsReceived": "Pachete receptionate", - "app.video.stats.packetsSent": "Pachete trimise", - "app.video.stats.packetsLost": "Pachete pierdute", - "app.video.stats.bitrate": "Bitrate", - "app.video.stats.lostPercentage": "Procent de Pierdere Total", - "app.video.stats.lostRecentPercentage": "Procent de Pierdere recent", - "app.video.stats.dimensions": "Dimensiuni", - "app.video.stats.codec": "Codec", - "app.video.stats.decodeDelay": "Intarziere de decodare", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Folosire decodare", - "app.video.stats.currentDelay": "Intarziere curenta", "app.fullscreenButton.label": "Faceti {0} ecran complet", "app.meeting.endNotification.ok.label": "OK", - "app.whiteboard.annotations.poll": "Rezultatul sondajului a fost publicat", "app.whiteboard.toolbar.tools": "Unelte", "app.whiteboard.toolbar.tools.hand": "Pan", "app.whiteboard.toolbar.tools.pencil": "Creion", @@ -643,7 +622,6 @@ "app.externalVideo.autoPlayWarning": "Rulati videoclipul pentru activarea sincronizarii", "app.network.connection.effective.slow": "Avem notificari pentru problemele de conectare.", "app.network.connection.effective.slow.help": "Mai multe informatii", - "app.externalVideo.noteLabel": "Nota: Partajarea videoclipurilor externe nu vor apare in inregistrare. Link-urile YouTube, Vimeo, Instructure media, Twitch si Daily Motion sunt suportate.", "app.actionsBar.actionsDropdown.shareExternalVideo": "Partajeaza un nou video extern", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Oprire partajare videoclip extern", "app.iOSWarning.label": "Va rog faceti upgrade la iOS 12.2 sau mai bun", diff --git a/bigbluebutton-html5/private/locales/ru.json b/bigbluebutton-html5/private/locales/ru.json index 748be1e5e6640b1a273985c6042562d811f84760..e184135edb27450378bf8f7fdc369c5eec299ecb 100644 --- a/bigbluebutton-html5/private/locales/ru.json +++ b/bigbluebutton-html5/private/locales/ru.json @@ -18,6 +18,7 @@ "app.chat.dropdown.save": "Сохранить", "app.chat.label": "Чат", "app.chat.offline": "Ðе в Ñети", + "app.chat.pollResult": "Результаты голоÑованиÑ", "app.chat.emptyLogLabel": "Журнал чата пуÑÑ‚", "app.chat.clearPublicChatMessage": "Журнал общего чата был очищен модератором", "app.chat.multi.typing": "ÐеÑколько пользователей набирают текÑÑ‚", @@ -50,15 +51,15 @@ "app.note.title": "Общие заметки", "app.note.label": "Заметка", "app.note.hideNoteLabel": "СпрÑтать заметку", + "app.note.tipLabel": "Ðажмите Esc, чтобы ÑфокуÑировать панель инÑтрументов редактора", "app.user.activityCheck": "Проверка активноÑти пользователÑ", "app.user.activityCheck.label": "Проверить находитÑÑ Ð»Ð¸ пользователь в конференции ({0})", "app.user.activityCheck.check": "Проверка", - "app.note.tipLabel": "Ðажмите Esc, чтобы ÑфокуÑировать панель инÑтрументов редактора", "app.userList.usersTitle": "Пользователи", "app.userList.participantsTitle": "УчаÑтники", "app.userList.messagesTitle": "СообщениÑ", "app.userList.notesTitle": "Заметки", - "app.userList.notesListItem.unreadContent": "Ð’ разделе \"Общие заметки\" поÑвилоÑÑŒ Ð½Ð¾Ð²Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ", + "app.userList.notesListItem.unreadContent": "Ð’ разделе 'Общие заметки' поÑвилоÑÑŒ Ð½Ð¾Ð²Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ", "app.userList.captionsTitle": "Субтитры", "app.userList.presenter": "Ведущий", "app.userList.you": "Ð’Ñ‹", @@ -125,8 +126,7 @@ "app.meeting.meetingTimeRemaining": "До Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ ÐºÐ¾Ð½Ñ„ÐµÑ€ÐµÐ½Ñ†Ð¸Ð¸ оÑталоÑÑŒ: {0}", "app.meeting.meetingTimeHasEnded": "Ð’Ñ€ÐµÐ¼Ñ Ð²Ñ‹ÑˆÐ»Ð¾. ÐšÐ¾Ð½Ñ„ÐµÑ€ÐµÐ½Ñ†Ð¸Ñ Ñкоро закроетÑÑ.", "app.meeting.endedMessage": "Ð’Ñ‹ будете перенаправлены назад на главный Ñкран", - "app.meeting.alertMeetingEndsUnderOneMinute": "ÐšÐ¾Ð½Ñ„ÐµÑ€ÐµÐ½Ñ†Ð¸Ñ Ð·Ð°ÐºÑ€Ð¾ÐµÑ‚ÑÑ Ñ‡ÐµÑ€ÐµÐ· 1 минуту", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Комната Ð´Ð»Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð¾Ð²Ð¾Ð¹ работы закроетÑÑ Ñ‡ÐµÑ€ÐµÐ· минуту", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "Звонок закончитÑÑ Ñ‡ÐµÑ€ÐµÐ· одну минуту.", "app.presentation.hide": "Скрыть презентацию", "app.presentation.notificationLabel": "Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð¿Ñ€ÐµÐ·ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ", "app.presentation.slideContent": "Содержимое Ñлайда", @@ -199,7 +199,7 @@ "app.poll.quickPollInstruction": "Выберите опцию ниже, чтобы начать голоÑование", "app.poll.customPollLabel": "Свой вариант голоÑованиÑ", "app.poll.startCustomLabel": "Ðачать Ñвой вариант голоÑованиÑ", - "app.poll.activePollInstruction": "ОÑтавьте данную панель открытой, чтобы увидеть ответы на ваш Ð¾Ð¿Ñ€Ð¾Ñ Ð² реальном времени. Когда вы будете готовы, выберите \"опубликовать результаты голоÑованиÑ\", чтобы опубликовать результаты и завершить опроÑ.", + "app.poll.activePollInstruction": "ОÑтавьте данную панель открытой, чтобы увидеть ответы на ваш Ð¾Ð¿Ñ€Ð¾Ñ Ð² реальном времени. Когда вы будете готовы, выберите 'опубликовать результаты голоÑованиÑ', чтобы опубликовать результаты и завершить опроÑ.", "app.poll.publishLabel": "Опубликовать результаты голоÑованиÑ", "app.poll.backLabel": "Ðазад к вариантам голоÑованиÑ", "app.poll.closeLabel": "Закрыть", @@ -264,7 +264,6 @@ "app.leaveConfirmation.confirmLabel": "Выйти", "app.leaveConfirmation.confirmDesc": "Выйти из конференции", "app.endMeeting.title": "Закончить конференцию", - "app.endMeeting.description": "Ð’Ñ‹ уверены, что хотите закончить Ñтот ÑеанÑ?", "app.endMeeting.yesLabel": "Да", "app.endMeeting.noLabel": "Ðет", "app.about.title": "О программе", @@ -285,10 +284,6 @@ "app.screenshare.screenShareLabel" : "Показ Ñкрана", "app.submenu.application.applicationSectionTitle": "Приложение", "app.submenu.application.animationsLabel": "ÐнимациÑ", - "app.submenu.application.audioAlertLabel": "Звуковые Ð¾Ð¿Ð¾Ð²ÐµÑ‰ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ñ‡Ð°Ñ‚Ð°", - "app.submenu.application.pushAlertLabel": "Ð’Ñплывающие Ð¾Ð¿Ð¾Ð²ÐµÑ‰ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ñ‡Ð°Ñ‚Ð°", - "app.submenu.application.userJoinAudioAlertLabel": "Ðудио оповещение о приÑоединении пользователÑ", - "app.submenu.application.userJoinPushAlertLabel": "Ð’Ñплывающее оповещение о приÑоединении пользователÑ", "app.submenu.application.fontSizeControlLabel": "Размер шрифта", "app.submenu.application.increaseFontBtnLabel": "Увеличить шрифт приложениÑ", "app.submenu.application.decreaseFontBtnLabel": "Уменьшить шрифт приложениÑ", @@ -345,25 +340,25 @@ "app.actionsBar.actionsDropdown.takePresenterDesc": "Ðазначить ÑÐµÐ±Ñ Ð½Ð¾Ð²Ñ‹Ð¼ ведущим", "app.actionsBar.emojiMenu.statusTriggerLabel": "УÑтановить ÑтатуÑ", "app.actionsBar.emojiMenu.awayLabel": "Отошел", - "app.actionsBar.emojiMenu.awayDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"Отошел\\\"", + "app.actionsBar.emojiMenu.awayDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"Отошел\"", "app.actionsBar.emojiMenu.raiseHandLabel": "ПоднÑть руку", "app.actionsBar.emojiMenu.raiseHandDesc": "ПоднÑть руку, чтобы задать вопроÑ", "app.actionsBar.emojiMenu.neutralLabel": "Ðерешительный", - "app.actionsBar.emojiMenu.neutralDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"Ðерешительный\\\"", + "app.actionsBar.emojiMenu.neutralDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"Ðерешительный\"", "app.actionsBar.emojiMenu.confusedLabel": "Смущен", - "app.actionsBar.emojiMenu.confusedDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"Смущен\\\"", + "app.actionsBar.emojiMenu.confusedDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"Смущен\"", "app.actionsBar.emojiMenu.sadLabel": "ГруÑтный", - "app.actionsBar.emojiMenu.sadDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"ГруÑтный\\\"", + "app.actionsBar.emojiMenu.sadDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"ГруÑтный\"", "app.actionsBar.emojiMenu.happyLabel": "СчаÑтливый", - "app.actionsBar.emojiMenu.happyDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"СчаÑтливый\\\"", + "app.actionsBar.emojiMenu.happyDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"СчаÑтливый\"", "app.actionsBar.emojiMenu.noneLabel": "ОчиÑтить ÑтатуÑ", "app.actionsBar.emojiMenu.noneDesc": "Очищает ваш ÑтатуÑ", "app.actionsBar.emojiMenu.applauseLabel": "ÐпплодиÑменты", - "app.actionsBar.emojiMenu.applauseDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"ÐплодиÑменты\\\"", + "app.actionsBar.emojiMenu.applauseDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"ÐплодиÑменты\"", "app.actionsBar.emojiMenu.thumbsUpLabel": "ÐравитÑÑ", - "app.actionsBar.emojiMenu.thumbsUpDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"ÐравитÑÑ\\\"", + "app.actionsBar.emojiMenu.thumbsUpDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"ÐравитÑÑ\"", "app.actionsBar.emojiMenu.thumbsDownLabel": "Ðе нравитÑÑ", - "app.actionsBar.emojiMenu.thumbsDownDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"Ðе нравитÑÑ\\\"", + "app.actionsBar.emojiMenu.thumbsDownDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"Ðе нравитÑÑ\"", "app.actionsBar.currentStatusDesc": "текущий ÑÑ‚Ð°Ñ‚ÑƒÑ {0}", "app.actionsBar.captions.start": "Ðачать проÑмотр Ñкрытых Ñубтитров", "app.actionsBar.captions.stop": "Прекратить проÑмотр Ñкрытых Ñубтитров", @@ -483,6 +478,8 @@ "app.userList.guest.pendingGuestUsers": "{0} Ожидающие гоÑти", "app.userList.guest.pendingGuestAlert": "ПриÑоединилÑÑ Ðº ÑеÑÑии и ждет вашего одобрениÑ", "app.userList.guest.rememberChoice": "Запомнить выбор", + "app.userList.guest.acceptLabel": "ПринÑть", + "app.userList.guest.denyLabel": "Отклонить", "app.user-info.title": "ПоиÑк в каталоге", "app.toast.breakoutRoomEnded": "Ð“Ñ€ÑƒÐ¿Ð¿Ð¾Ð²Ð°Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ð° закончилаÑÑŒ. ПожалуйÑта, приÑоединитеÑÑŒ Ñнова к аудио конференции.", "app.toast.chat.public": "Ðовое Ñообщение в публичном чате", @@ -497,6 +494,7 @@ "app.notification.recordingPaused": "Ðтот ÑÐµÐ°Ð½Ñ Ð±Ð¾Ð»ÑŒÑˆÐµ не будет запиÑан", "app.notification.recordingAriaLabel": "ПродолжительноÑть запиÑи", "app.notification.userJoinPushAlert": "{0} приÑоединилÑÑ Ðº ÑеанÑу.", + "app.submenu.notification.raiseHandLabel": "ПоднÑть руку", "app.shortcut-help.title": "Клавиши быÑтрого доÑтупа", "app.shortcut-help.accessKeyNotAvailable": "Клавиши быÑтрого доÑтупа недоÑтупны", "app.shortcut-help.comboLabel": "Комбо", @@ -530,6 +528,7 @@ "app.lock-viewers.button.cancel": "Отмена", "app.lock-viewers.locked": "Заблокировано", "app.lock-viewers.unlocked": "Разблокирован", + "app.connection-status.offline": "Ðе в Ñети", "app.recording.startTitle": "Включить запиÑÑŒ", "app.recording.stopTitle": "ПоÑтавить запиÑÑŒ на паузу", "app.recording.resumeTitle": "Возобновить запиÑÑŒ", @@ -541,6 +540,7 @@ "app.videoPreview.closeLabel": "Закрыть", "app.videoPreview.findingWebcamsLabel": "Ищем вÑбкамеры", "app.videoPreview.startSharingLabel": "Ðачать транÑлÑцию Ñ Ð²Ñб-камеры", + "app.videoPreview.stopSharingLabel": "ОÑтановить демонÑтрацию Ñкрана", "app.videoPreview.webcamOptionLabel": "Выбрать вÑбкамеру", "app.videoPreview.webcamPreviewLabel": "Предварительный проÑмотр вÑбкамеры", "app.videoPreview.webcamSettingsTitle": "ÐаÑтройки вÑбкамеры", @@ -570,19 +570,6 @@ "app.video.videoMenuDesc": "Открыть выпадающее меню видео", "app.video.chromeExtensionError": "Ð’Ñ‹ должны уÑтановить", "app.video.chromeExtensionErrorLink": "Ñто раÑширение Chrome", - "app.video.stats.title": "СтатиÑтика подключений", - "app.video.stats.packetsReceived": "Полученные пакеты", - "app.video.stats.packetsSent": "Пакеты отправлены", - "app.video.stats.packetsLost": "Пакеты потерÑны", - "app.video.stats.bitrate": "Битрейт", - "app.video.stats.lostPercentage": "Общий процент потерÑнных", - "app.video.stats.lostRecentPercentage": "Ðынешний процент потерÑнных", - "app.video.stats.dimensions": "Размеры", - "app.video.stats.codec": "Кодек", - "app.video.stats.decodeDelay": "Задержка декодированиÑ", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "ИÑпользование кодировкой", - "app.video.stats.currentDelay": "Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð·Ð°Ð´ÐµÑ€Ð¶ÐºÐ°", "app.fullscreenButton.label": "Включить {0} на полный Ñкран", "app.deskshare.iceConnectionStateError": "Ðе удалоÑÑŒ уÑтановить Ñоединение при ÑовмеÑтном проÑмотре Ñкрана (ICE error 1108)", "app.sfu.mediaServerConnectionError2000": "Ðевозможно подключитьÑÑ Ðº медиа-Ñерверу (error 2000)", @@ -595,7 +582,6 @@ "app.sfu.invalidSdp2202":"Клиент Ñгенерировал недейÑтвительный мультимедиа Ð·Ð°Ð¿Ñ€Ð¾Ñ (SDP error 2202)", "app.sfu.noAvailableCodec2203": "Серверу не удалоÑÑŒ найти подходÑщий кодек (error 2203)", "app.meeting.endNotification.ok.label": "ОК", - "app.whiteboard.annotations.poll": "Результаты опроÑа были опубликованы", "app.whiteboard.toolbar.tools": "ИнÑтрументы", "app.whiteboard.toolbar.tools.hand": "Панорамирование", "app.whiteboard.toolbar.tools.pencil": "Карандаш", @@ -676,7 +662,6 @@ "app.externalVideo.autoPlayWarning": "ЗапуÑтите видео, чтобы активировать Ñинхронизацию медиа", "app.network.connection.effective.slow": "ÐаблюдаютÑÑ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ñ‹ Ñ Ñоединением", "app.network.connection.effective.slow.help": "ÐŸÐ¾Ð´Ñ€Ð¾Ð±Ð½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ", - "app.externalVideo.noteLabel": "Заметка: видео Ñо внешних реÑурÑов не будет отображатьÑÑ Ð² запиÑи. ПоддерживаютÑÑ ÑÑылки YouTube, Vimeo, Instructure Media, Twitch и Daily Motion.", "app.actionsBar.actionsDropdown.shareExternalVideo": "ПоделитьÑÑ Ð²Ð¸Ð´ÐµÐ¾ Ñ Ð²Ð½ÐµÑˆÐ½Ð¸Ñ… реÑурÑов", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Прекратить показ видео Ñ Ð²Ð½ÐµÑˆÐ½Ð¸Ñ… реÑурÑов", "app.iOSWarning.label": "ПожалуйÑта, обновитеÑÑŒ до iOS 12.2 или более новой верÑии", diff --git a/bigbluebutton-html5/private/locales/ru_RU.json b/bigbluebutton-html5/private/locales/ru_RU.json index 0515dab5ebda7563f6ed650634d85660cb4b5e56..75c6c37945a66605fdd5be8aa9b0a21c256672ad 100644 --- a/bigbluebutton-html5/private/locales/ru_RU.json +++ b/bigbluebutton-html5/private/locales/ru_RU.json @@ -50,15 +50,15 @@ "app.note.title": "Общие заметки", "app.note.label": "Заметка", "app.note.hideNoteLabel": "СпрÑтать заметку", + "app.note.tipLabel": "Ðажмите Esc, чтобы ÑфокуÑировать панель инÑтрументов редактора", "app.user.activityCheck": "Проверка активноÑти пользователÑ", "app.user.activityCheck.label": "Проверить находитÑÑ Ð»Ð¸ пользователь в конференции ({0})", "app.user.activityCheck.check": "Проверка", - "app.note.tipLabel": "Ðажмите Esc, чтобы ÑфокуÑировать панель инÑтрументов редактора", "app.userList.usersTitle": "Пользователи", "app.userList.participantsTitle": "УчаÑтники", "app.userList.messagesTitle": "СообщениÑ", "app.userList.notesTitle": "Заметки", - "app.userList.notesListItem.unreadContent": "Ð’ разделе \"Общие заметки\" поÑвилоÑÑŒ Ð½Ð¾Ð²Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ", + "app.userList.notesListItem.unreadContent": "Ð’ разделе 'Общие заметки' поÑвилоÑÑŒ Ð½Ð¾Ð²Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ", "app.userList.captionsTitle": "Субтитры", "app.userList.presenter": "Ведущий", "app.userList.you": "Ð’Ñ‹", @@ -122,8 +122,6 @@ "app.meeting.meetingTimeRemaining": "До Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ ÐºÐ¾Ð½Ñ„ÐµÑ€ÐµÐ½Ñ†Ð¸Ð¸ оÑталоÑÑŒ: {0}", "app.meeting.meetingTimeHasEnded": "Ð’Ñ€ÐµÐ¼Ñ Ð²Ñ‹ÑˆÐ»Ð¾. ÐšÐ¾Ð½Ñ„ÐµÑ€ÐµÐ½Ñ†Ð¸Ñ Ñкоро закроетÑÑ.", "app.meeting.endedMessage": "Ð’Ñ‹ будете перенаправлены назад на главный Ñкран", - "app.meeting.alertMeetingEndsUnderOneMinute": "ÐšÐ¾Ð½Ñ„ÐµÑ€ÐµÐ½Ñ†Ð¸Ñ Ð·Ð°ÐºÑ€Ð¾ÐµÑ‚ÑÑ Ñ‡ÐµÑ€ÐµÐ· 1 минуту", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Комната Ð´Ð»Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð¾Ð²Ð¾Ð¹ работы закроетÑÑ Ñ‡ÐµÑ€ÐµÐ· минуту", "app.presentation.hide": "Скрыть презентацию", "app.presentation.notificationLabel": "Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð¿Ñ€ÐµÐ·ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ", "app.presentation.slideContent": "Содержимое Ñлайда", @@ -155,7 +153,7 @@ "app.presentation.presentationToolbar.fitToPage": "Подогнать по размеру Ñтраницы", "app.presentation.presentationToolbar.goToSlide": "Слайд {0}", "app.presentationUploder.title": "ПрезентациÑ", - "app.presentationUploder.message": "Как ведущий, вы можете загрузить любой офиÑный документ или файл PDF. Ð”Ð»Ñ Ð»ÑƒÑ‡ÑˆÐµÐ³Ð¾ результата, мы рекомендуем загружать PDF.\nПожалуйÑта убедитеÑÑŒ, что Ð¿Ñ€ÐµÐ·ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ Ð²Ñ‹Ð±Ñ€Ð°Ð½Ð° Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ круглого флажка Ñ Ð¿Ñ€Ð°Ð²Ð¾Ð¹ Ñтороны.", + "app.presentationUploder.message": "Как ведущий, вы можете загрузить любой офиÑный документ или файл PDF. Ð”Ð»Ñ Ð»ÑƒÑ‡ÑˆÐµÐ³Ð¾ результата, мы рекомендуем загружать PDF. ПожалуйÑта убедитеÑÑŒ, что Ð¿Ñ€ÐµÐ·ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ Ð²Ñ‹Ð±Ñ€Ð°Ð½Ð° Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ круглого флажка Ñ Ð¿Ñ€Ð°Ð²Ð¾Ð¹ Ñтороны.", "app.presentationUploder.uploadLabel": "Загрузить", "app.presentationUploder.confirmLabel": "Подтвердить", "app.presentationUploder.confirmDesc": "Сохранить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸ начать презентацию", @@ -195,7 +193,7 @@ "app.poll.quickPollInstruction": "Выберите опцию ниже, чтобы начать голоÑование", "app.poll.customPollLabel": "Свой вариант голоÑованиÑ", "app.poll.startCustomLabel": "Ðачать Ñвой вариант голоÑованиÑ", - "app.poll.activePollInstruction": "ОÑтавьте данную панель открытой, чтобы увидеть ответы на ваш Ð¾Ð¿Ñ€Ð¾Ñ Ð² реальном времени. Когда вы будете готовы, выберите \"опубликовать результаты голоÑованиÑ\", чтобы опубликовать результаты и завершить опроÑ.", + "app.poll.activePollInstruction": "ОÑтавьте данную панель открытой, чтобы увидеть ответы на ваш Ð¾Ð¿Ñ€Ð¾Ñ Ð² реальном времени. Когда вы будете готовы, выберите "опубликовать результаты голоÑованиÑ", чтобы опубликовать результаты и завершить опроÑ.", "app.poll.publishLabel": "Опубликовать результаты голоÑованиÑ", "app.poll.backLabel": "Ðазад к вариантам голоÑованиÑ", "app.poll.closeLabel": "Закрыть", @@ -260,7 +258,6 @@ "app.leaveConfirmation.confirmLabel": "Выйти", "app.leaveConfirmation.confirmDesc": "Выйти из конференции", "app.endMeeting.title": "Закончить конференцию", - "app.endMeeting.description": "Ð’Ñ‹ уверены, что хотите закончить Ñтот ÑеанÑ?", "app.endMeeting.yesLabel": "Да", "app.endMeeting.noLabel": "Ðет", "app.about.title": "О программе", @@ -281,10 +278,6 @@ "app.screenshare.screenShareLabel" : "Показ Ñкрана", "app.submenu.application.applicationSectionTitle": "Приложение", "app.submenu.application.animationsLabel": "ÐнимациÑ", - "app.submenu.application.audioAlertLabel": "Звуковые Ð¾Ð¿Ð¾Ð²ÐµÑ‰ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ñ‡Ð°Ñ‚Ð°", - "app.submenu.application.pushAlertLabel": "Ð’Ñплывающие Ð¾Ð¿Ð¾Ð²ÐµÑ‰ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ñ‡Ð°Ñ‚Ð°", - "app.submenu.application.userJoinAudioAlertLabel": "Ðудио оповещение о приÑоединении пользователÑ", - "app.submenu.application.userJoinPushAlertLabel": "Ð’Ñплывающее оповещение о приÑоединении пользователÑ", "app.submenu.application.fontSizeControlLabel": "Размер шрифта", "app.submenu.application.increaseFontBtnLabel": "Увеличить шрифт приложениÑ", "app.submenu.application.decreaseFontBtnLabel": "Уменьшить шрифт приложениÑ", @@ -341,25 +334,25 @@ "app.actionsBar.actionsDropdown.takePresenterDesc": "Ðазначить ÑÐµÐ±Ñ Ð½Ð¾Ð²Ñ‹Ð¼ ведущим", "app.actionsBar.emojiMenu.statusTriggerLabel": "УÑтановить ÑтатуÑ", "app.actionsBar.emojiMenu.awayLabel": "Отошел", - "app.actionsBar.emojiMenu.awayDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"Отошел\\\"", + "app.actionsBar.emojiMenu.awayDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"Отошел\"", "app.actionsBar.emojiMenu.raiseHandLabel": "ПоднÑть руку", "app.actionsBar.emojiMenu.raiseHandDesc": "ПоднÑть руку, чтобы задать вопроÑ", "app.actionsBar.emojiMenu.neutralLabel": "Ðерешительный", - "app.actionsBar.emojiMenu.neutralDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"Ðерешительный\\\"", + "app.actionsBar.emojiMenu.neutralDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"Ðерешительный\"", "app.actionsBar.emojiMenu.confusedLabel": "Смущен", - "app.actionsBar.emojiMenu.confusedDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"Смущен\\\"", + "app.actionsBar.emojiMenu.confusedDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"Смущен\"", "app.actionsBar.emojiMenu.sadLabel": "ГруÑтный", - "app.actionsBar.emojiMenu.sadDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"ГруÑтный\\\"", + "app.actionsBar.emojiMenu.sadDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"ГруÑтный\"", "app.actionsBar.emojiMenu.happyLabel": "СчаÑтливый", - "app.actionsBar.emojiMenu.happyDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"СчаÑтливый\\\"", + "app.actionsBar.emojiMenu.happyDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"СчаÑтливый\"", "app.actionsBar.emojiMenu.noneLabel": "ОчиÑтить ÑтатуÑ", "app.actionsBar.emojiMenu.noneDesc": "Очищает ваш ÑтатуÑ", "app.actionsBar.emojiMenu.applauseLabel": "ÐпплодиÑменты", - "app.actionsBar.emojiMenu.applauseDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"ÐплодиÑменты\\\"", + "app.actionsBar.emojiMenu.applauseDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"ÐплодиÑменты\"", "app.actionsBar.emojiMenu.thumbsUpLabel": "ÐравитÑÑ", - "app.actionsBar.emojiMenu.thumbsUpDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"ÐравитÑÑ\\\"", + "app.actionsBar.emojiMenu.thumbsUpDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"ÐравитÑÑ\"", "app.actionsBar.emojiMenu.thumbsDownLabel": "Ðе нравитÑÑ", - "app.actionsBar.emojiMenu.thumbsDownDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"Ðе нравитÑÑ\\\"", + "app.actionsBar.emojiMenu.thumbsDownDesc": "ИзменÑет ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"Ðе нравитÑÑ\"", "app.actionsBar.currentStatusDesc": "текущий ÑÑ‚Ð°Ñ‚ÑƒÑ {0}", "app.actionsBar.captions.start": "Ðачать проÑмотр Ñкрытых Ñубтитров", "app.actionsBar.captions.stop": "Прекратить проÑмотр Ñкрытых Ñубтитров", @@ -564,22 +557,8 @@ "app.video.videoMenuDesc": "Открыть выпадающее меню видео", "app.video.chromeExtensionError": "Ð’Ñ‹ должны уÑтановить", "app.video.chromeExtensionErrorLink": "Ñто раÑширение Chrome", - "app.video.stats.title": "СтатиÑтика подключений", - "app.video.stats.packetsReceived": "Полученные пакеты", - "app.video.stats.packetsSent": "Пакеты отправлены", - "app.video.stats.packetsLost": "Пакеты потерÑны", - "app.video.stats.bitrate": "Битрейт", - "app.video.stats.lostPercentage": "Общий процент потерÑнных", - "app.video.stats.lostRecentPercentage": "Ðынешний процент потерÑнных", - "app.video.stats.dimensions": "Размеры", - "app.video.stats.codec": "Кодек", - "app.video.stats.decodeDelay": "Задержка декодированиÑ", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "ИÑпользование кодировкой", - "app.video.stats.currentDelay": "Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð·Ð°Ð´ÐµÑ€Ð¶ÐºÐ°", "app.fullscreenButton.label": "Включить {0} на полный Ñкран", "app.meeting.endNotification.ok.label": "ОК", - "app.whiteboard.annotations.poll": "Результаты опроÑа были опубликованы", "app.whiteboard.toolbar.tools": "ИнÑтрументы", "app.whiteboard.toolbar.tools.hand": "Панорамирование", "app.whiteboard.toolbar.tools.pencil": "Карандаш", @@ -660,7 +639,6 @@ "app.externalVideo.autoPlayWarning": "ЗапуÑтите видео, чтобы активировать Ñинхронизацию медиа", "app.network.connection.effective.slow": "ÐаблюдаютÑÑ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ñ‹ Ñ Ñоединением", "app.network.connection.effective.slow.help": "ÐŸÐ¾Ð´Ñ€Ð¾Ð±Ð½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ", - "app.externalVideo.noteLabel": "Заметка: видео Ñо внешних реÑурÑов не будет отображатьÑÑ Ð² запиÑи. ПоддерживаютÑÑ ÑÑылки YouTube, Vimeo, Instructure Media, Twitch и Daily Motion.", "app.actionsBar.actionsDropdown.shareExternalVideo": "ПоделитьÑÑ Ð²Ð¸Ð´ÐµÐ¾ Ñ Ð²Ð½ÐµÑˆÐ½Ð¸Ñ… реÑурÑов", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Прекратить показ видео Ñ Ð²Ð½ÐµÑˆÐ½Ð¸Ñ… реÑурÑов", "app.iOSWarning.label": "ПожалуйÑта, обновитеÑÑŒ до iOS 12.2 или более новой верÑии", diff --git a/bigbluebutton-html5/private/locales/sk_SK.json b/bigbluebutton-html5/private/locales/sk_SK.json index 538ef8af208a5235260eed3d901c3b132efcbfcf..f4d54c47480b349b9cff1398975f306a541aa68d 100644 --- a/bigbluebutton-html5/private/locales/sk_SK.json +++ b/bigbluebutton-html5/private/locales/sk_SK.json @@ -18,6 +18,7 @@ "app.chat.dropdown.save": "UložiÅ¥ chat", "app.chat.label": "Chat", "app.chat.offline": "Vypnutý", + "app.chat.pollResult": "Výsledky ankety", "app.chat.emptyLogLabel": "Log chatu je prázdny", "app.chat.clearPublicChatMessage": "História chatu bola vymazaná moderátorom", "app.chat.multi.typing": "Viacero užÃvateľov pÃÅ¡e", @@ -50,10 +51,10 @@ "app.note.title": "Zdieľané poznámky", "app.note.label": "Poznámka", "app.note.hideNoteLabel": "SkryÅ¥ poznámku", + "app.note.tipLabel": "StlaÄte Esc pre vstup do liÅ¡ty nástrojov editora", "app.user.activityCheck": "Kontrola aktivÃt užÃvateľa", "app.user.activityCheck.label": "Kontrola, Äi je užÃvateľ pripojený ({0})", "app.user.activityCheck.check": "Kontrola", - "app.note.tipLabel": "StlaÄte Esc pre vstup do liÅ¡ty nástrojov editora", "app.userList.usersTitle": "UžÃvatelia", "app.userList.participantsTitle": "ÚÄastnÃci", "app.userList.messagesTitle": "Správy", @@ -63,6 +64,7 @@ "app.userList.presenter": "Prednášajúci", "app.userList.you": "Vy", "app.userList.locked": "Zamknutý", + "app.userList.byModerator": "od (Moderator)", "app.userList.label": "Zoznam užÃvateľov", "app.userList.toggleCompactView.label": "Prepnúť na kompaktné rozloženie", "app.userList.guest": "HosÅ¥", @@ -72,6 +74,8 @@ "app.userList.menu.chat.label": "ZaÄnite súkromný chat", "app.userList.menu.clearStatus.label": "VyÄistiÅ¥ stav", "app.userList.menu.removeUser.label": "OdstrániÅ¥ užÃvateľa", + "app.userList.menu.removeConfirmation.label": "OdstrániÅ¥ užÃvateľa ({0})", + "app.userlist.menu.removeConfirmation.desc": "ZabrániÅ¥ tomuto užÃvateľovi znova sa pripojiÅ¥.", "app.userList.menu.muteUserAudio.label": "StlmiÅ¥ užÃvateľa", "app.userList.menu.unmuteUserAudio.label": "ZruÅ¡iÅ¥ stlmenie užÃvateľa", "app.userList.userAriaLabel": "{0} {1} {2} Stav {3}", @@ -92,6 +96,8 @@ "app.userList.userOptions.unmuteAllDesc": "ZruÅ¡it stlmenie užÃvateľov", "app.userList.userOptions.lockViewersLabel": "Uzamknúť funkcie pre poslucháÄov", "app.userList.userOptions.lockViewersDesc": "Zamkne pre poslucháÄov niektoré funkcionality", + "app.userList.userOptions.connectionStatusLabel": "Stav spojenia", + "app.userList.userOptions.connectionStatusDesc": "ZobraziÅ¥ stav spojenia užÃvateľov", "app.userList.userOptions.disableCam": "Webkamery poslucháÄov sú vypnuté", "app.userList.userOptions.disableMic": "Mikrofóny poslucháÄov sú vypnuté", "app.userList.userOptions.disablePrivChat": "Súkromný chat je vypnutý", @@ -111,6 +117,8 @@ "app.media.autoplayAlertDesc": "PovoliÅ¥ prÃstup", "app.media.screenshare.start": "Zdieľanie obrazovky bolo spustené", "app.media.screenshare.end": "Zdieľanie obrazovky bolo ukonÄené", + "app.media.screenshare.unavailable": "Zdieľanie obrazovky je nedostupné", + "app.media.screenshare.notSupported": "Zdieľanie obrazovky nie je dostupné v tomto prehliadaÄi.", "app.media.screenshare.autoplayBlockedDesc": "Potrebujeme od Vás povolenie pre zobrazenie obrazovky prednášajúceho.", "app.media.screenshare.autoplayAllowLabel": "ZobraziÅ¥ zdieľanú obrazovku", "app.screenshare.notAllowed": "Chyba: Právo na zdieľanie obrazovky nebolo povolené.", @@ -121,8 +129,10 @@ "app.meeting.meetingTimeRemaining": "Zostávajúci Äas: {0}", "app.meeting.meetingTimeHasEnded": "ÄŒas vyprÅ¡al. Konferencia sa Äoskoro skonÄÃ", "app.meeting.endedMessage": "Budete presmerovaný(á) na VaÅ¡u domovskú obrazovku", - "app.meeting.alertMeetingEndsUnderOneMinute": "Konferencia bude ukonÄená behom minúty.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "VedľajÅ¡ia miestnosÅ¥ bude ukonÄená behom minúty", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "Konferencia skonÄà o 1 minútu.", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "Konferencia skonÄà o {0} minút.", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "VedľajÅ¡ie miestnosti skonÄia o {0} minút.", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "VedľajÅ¡ie miestnosti skonÄia o 1 minútu.", "app.presentation.hide": "SkyÅ¥ prezentáciu", "app.presentation.notificationLabel": "Aktuálna prezentácia", "app.presentation.slideContent": "Obsah snÃmku", @@ -134,8 +144,8 @@ "app.presentation.presentationToolbar.selectLabel": "Vyberte si snÃmok", "app.presentation.presentationToolbar.prevSlideLabel": "Predchádzajúci snÃmok", "app.presentation.presentationToolbar.prevSlideDesc": "Posunie prezentáciu na predchádzajúci snÃmok", - "app.presentation.presentationToolbar.nextSlideLabel": "ÄŽalšà snÃmok", - "app.presentation.presentationToolbar.nextSlideDesc": "Posunie prezentáciu na Äalšà snÃmok", + "app.presentation.presentationToolbar.nextSlideLabel": "Nasledujúci snÃmok", + "app.presentation.presentationToolbar.nextSlideDesc": "Posunie prezentáciu na nasledujúci snÃmok", "app.presentation.presentationToolbar.skipSlideLabel": "PreskoÄiÅ¥ snÃmok", "app.presentation.presentationToolbar.skipSlideDesc": "Posunie prezentáciu na vybraný snÃmok", "app.presentation.presentationToolbar.fitWidthLabel": "PrispôsobiÅ¥ na Å¡Ãrku", @@ -169,13 +179,20 @@ "app.presentationUploder.rejectedError": "Vybratý súbor bol odmietnutý. prosÃm skontrolujte typ súboru.", "app.presentationUploder.upload.progress": "Nahrávanie ({0}%)", "app.presentationUploder.upload.413": "Súbor je prÃliÅ¡ veľký. ProsÃm rozdeľte ho na viacero menÅ¡Ãch súborov.", + "app.presentationUploder.upload.408": "Požiadavke pre nahratie súboru vyprÅ¡al Äasový limit.", + "app.presentationUploder.upload.404": "404: Neplatná znaÄka pre nahratie súboru", + "app.presentationUploder.upload.401": "Požiadavka pre nahratie prezentácie zlyhala.", "app.presentationUploder.conversion.conversionProcessingSlides": "Spracúvam stránku {0} z {1}", "app.presentationUploder.conversion.genericConversionStatus": "Konvertujem súbor ...", "app.presentationUploder.conversion.generatingThumbnail": "Generujem náhľady ...", "app.presentationUploder.conversion.generatedSlides": "SnÃmky boli vygenerované ...", "app.presentationUploder.conversion.generatingSvg": "Generujem obrázky SVG ...", + "app.presentationUploder.conversion.pageCountExceeded": "PrekroÄený maximálny poÄet strán. ProsÃm rozdeľte súbor na viacero menÅ¡Ãch súborov.", + "app.presentationUploder.conversion.officeDocConversionInvalid": "Spracovanie dokumentu office bolo neúspeÅ¡né. ProsÃm nahrajte dokument vo formáte PDF.", + "app.presentationUploder.conversion.officeDocConversionFailed": "Spracovanie dokumentu office bolo neúspeÅ¡né. ProsÃm nahrajte dokument vo formáte PDF.", "app.presentationUploder.conversion.pdfHasBigPage": "Nebolo možné skonvertovaÅ¥ .PDF súbor, prosÃm skúste ho optimalizovaÅ¥", "app.presentationUploder.conversion.timeout": "Ops, konverzia trvala prÃliÅ¡ dlho", + "app.presentationUploder.conversion.pageCountFailed": "Nepodarilo sa zistiÅ¥ poÄet strán.", "app.presentationUploder.isDownloadableLabel": "ZakázaÅ¥ sÅ¥ahovanie prezentácie", "app.presentationUploder.isNotDownloadableLabel": "ZakázaÅ¥ sÅ¥ahovanie prezentácie", "app.presentationUploder.removePresentationLabel": "OdstrániÅ¥ prezentáciu", @@ -255,7 +272,7 @@ "app.leaveConfirmation.confirmLabel": "OpustiÅ¥", "app.leaveConfirmation.confirmDesc": "Opustenie tejto konferencie", "app.endMeeting.title": "UkonÄiÅ¥ konferenciu", - "app.endMeeting.description": "Ste si istý(á), že chcete ukonÄiÅ¥ túto konferenciu?", + "app.endMeeting.description": "UrÄite chcete ukonÄiÅ¥ túto konferenciu (vÅ¡etci úÄastnÃci budú odpojenÃ!) ?", "app.endMeeting.yesLabel": "Ãno", "app.endMeeting.noLabel": "Nie", "app.about.title": "O aplikácii", @@ -276,17 +293,19 @@ "app.screenshare.screenShareLabel" : "Zdieľanie obrazovky", "app.submenu.application.applicationSectionTitle": "Aplikácia", "app.submenu.application.animationsLabel": "Animácie", - "app.submenu.application.audioAlertLabel": "Akustické upozornenia pre chat", - "app.submenu.application.pushAlertLabel": "Vyskakovacie upozornenia pre chat", - "app.submenu.application.userJoinAudioAlertLabel": "Akustické upozornenia pre pripojenie užÃvateľa", - "app.submenu.application.userJoinPushAlertLabel": "Vyskakovacie upozornenia pre pripojenie užÃvateľa", - "app.submenu.application.fontSizeControlLabel": "Veľkost pÃsma", + "app.submenu.application.fontSizeControlLabel": "VeľkosÅ¥ pÃsma", "app.submenu.application.increaseFontBtnLabel": "ZväÄÅ¡iÅ¥ pÃsmo aplikácie", "app.submenu.application.decreaseFontBtnLabel": "ZmenÅ¡iÅ¥ pÃsmo aplikácie", "app.submenu.application.currentSize": "aktuálne {0}", "app.submenu.application.languageLabel": "Jazyk aplikácie", "app.submenu.application.languageOptionLabel": "Vyberte jazyk", "app.submenu.application.noLocaleOptionLabel": "Žiadne jazyky k dispozÃcii", + "app.submenu.notification.SectionTitle": "Upozornenia", + "app.submenu.notification.Desc": "Definujte si ako a o Äom budete dostávaÅ¥ upozornenia.", + "app.submenu.notification.audioAlertLabel": "Zvukové upozornenia", + "app.submenu.notification.pushAlertLabel": "Vyskakovacie okná", + "app.submenu.notification.messagesLabel": "Chat správa", + "app.submenu.notification.userJoinLabel": "Pripojenie užÃvateľa", "app.submenu.audio.micSourceLabel": "Zdroj signálu pre mikrofón", "app.submenu.audio.speakerSourceLabel": "Zdroj signálu pre reproduktor", "app.submenu.audio.streamVolumeLabel": "VaÅ¡a hlasitosÅ¥ zvuku", @@ -462,6 +481,8 @@ "app.userList.guest.pendingGuestUsers": "{0} Äakajúcich hostÃ", "app.userList.guest.pendingGuestAlert": "sa pripojil a Äaká na VaÅ¡e povolenie.", "app.userList.guest.rememberChoice": "ZapamätaÅ¥ voľbu", + "app.userList.guest.acceptLabel": "PovoliÅ¥", + "app.userList.guest.denyLabel": "ZakázaÅ¥", "app.user-info.title": "VyhľadaÅ¥ v adresári", "app.toast.breakoutRoomEnded": "VedľajÅ¡ia miestnosÅ¥ bola ukonÄená. ProsÃm, pripojte se znovu k audiu.", "app.toast.chat.public": "Nová správa vo verejnom chate", @@ -476,6 +497,7 @@ "app.notification.recordingPaused": "Nahrávanie už nie je zapnuté", "app.notification.recordingAriaLabel": "ÄŒas záznamu ", "app.notification.userJoinPushAlert": "{0} sa pripojil", + "app.submenu.notification.raiseHandLabel": "HlásiÅ¥ sa o slovo", "app.shortcut-help.title": "Klávesové skratky", "app.shortcut-help.accessKeyNotAvailable": "PrÃstupové klávesy nie sú dostupné", "app.shortcut-help.comboLabel": "Kombinácia", @@ -491,7 +513,7 @@ "app.shortcut-help.openActions": "OtvoriÅ¥ menu akciÃ", "app.shortcut-help.openStatus": "OtvoriÅ¥ menu stavov", "app.shortcut-help.togglePan": "AktivovaÅ¥ nástroje tabule (Prednášajúci)", - "app.shortcut-help.nextSlideDesc": "Nasledovný snÃmok (Prednášajúci)", + "app.shortcut-help.nextSlideDesc": "Nasledujúci snÃmok (Prednášajúci)", "app.shortcut-help.previousSlideDesc": "Predchádzajúci snÃmok (Prednášajúci)", "app.lock-viewers.title": "Uzamknúť funkcie pre poslucháÄov", "app.lock-viewers.description": "Pomocou týchto volieb dokážete obmedziÅ¥ prÃstup poslucháÄov k Å¡pecifických funkciám.", @@ -509,17 +531,25 @@ "app.lock-viewers.button.cancel": "ZruÅ¡iÅ¥", "app.lock-viewers.locked": "Uzamknuté", "app.lock-viewers.unlocked": "Odomknuté", + "app.connection-status.ariaTitle": "Informácia o stave spojenia", + "app.connection-status.title": "Stav spojenia", + "app.connection-status.description": "ZobraziÅ¥ stav spojenia užÃvateľov", + "app.connection-status.empty": "Doteraz nebol nahlásený žiadny problém so spojenÃm", + "app.connection-status.more": "viac", + "app.connection-status.offline": "spojenie preruÅ¡ené", "app.recording.startTitle": "ZaÄaÅ¥ nahrávanie", - "app.recording.stopTitle": "PozastaviÅ¥ nahrávanie", + "app.recording.stopTitle": "PreruÅ¡iÅ¥ nahrávanie", "app.recording.resumeTitle": "ObnoviÅ¥ nahrávanie", - "app.recording.startDescription": "Opätovným kliknutÃm na nahrávacie tlaÄÃtko môžete neskôr nahrávanie pozastaviÅ¥.", - "app.recording.stopDescription": "Ste si istý(á), že chcete nahrávanie pozastaviÅ¥? V nahrávanà môžete pokraÄovaÅ¥ neskôr po opätovným kliknutà na nahrávacie tlaÄÃtko.", + "app.recording.startDescription": "Opätovným kliknutÃm na nahrávacie tlaÄÃtko môžete neskôr nahrávanie preruÅ¡iÅ¥.", + "app.recording.stopDescription": "Ste si istý(á), že chcete preruÅ¡iÅ¥ nahrávanie? V nahrávanà môžete pokraÄovaÅ¥ neskôr po opätovnom kliknutà na nahrávacie tlaÄÃtko.", "app.videoPreview.cameraLabel": "Kamera", "app.videoPreview.profileLabel": "Kvalita", "app.videoPreview.cancelLabel": "ZruÅ¡iÅ¥", "app.videoPreview.closeLabel": "ZavrieÅ¥", "app.videoPreview.findingWebcamsLabel": "HľadaÅ¥ webkameru", "app.videoPreview.startSharingLabel": "ZaÄaÅ¥ zdieľanie", + "app.videoPreview.stopSharingLabel": "UkonÄiÅ¥ zdieľanie", + "app.videoPreview.sharedCameraLabel": "Táto kamera je už zdieľaná", "app.videoPreview.webcamOptionLabel": "VybraÅ¥ webkameru", "app.videoPreview.webcamPreviewLabel": "Náhľad webkamery", "app.videoPreview.webcamSettingsTitle": "Nastavenie webkamery", @@ -547,22 +577,8 @@ "app.video.videoMenuDesc": "OtvoriÅ¥ video menu", "app.video.chromeExtensionError": "MusÃte nainÅ¡talovaÅ¥", "app.video.chromeExtensionErrorLink": "toto rozÅ¡Ãrenie pre Chrome", - "app.video.stats.title": "Å tatistiky pripojenia", - "app.video.stats.packetsReceived": "Paketov prijatých", - "app.video.stats.packetsSent": "Paketov odoslaných", - "app.video.stats.packetsLost": "Paketov stratených", - "app.video.stats.bitrate": "Bitrate", - "app.video.stats.lostPercentage": "Percento stratených", - "app.video.stats.lostRecentPercentage": "Percento naposledy stratených", - "app.video.stats.dimensions": "Rozmery", - "app.video.stats.codec": "Kodek", - "app.video.stats.decodeDelay": "Oneskorenie pri dekódovanÃ", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Využitie enkodéra", - "app.video.stats.currentDelay": "Aktuálne oneskorenie", "app.fullscreenButton.label": "NastaviÅ¥ {0} na celú obrazovku", "app.meeting.endNotification.ok.label": "OK", - "app.whiteboard.annotations.poll": "Výsledky ankety boli publikované", "app.whiteboard.toolbar.tools": "Nástroje tabule", "app.whiteboard.toolbar.tools.hand": "Posun", "app.whiteboard.toolbar.tools.pencil": "ZvýrazňovaÄ", @@ -643,7 +659,7 @@ "app.externalVideo.autoPlayWarning": "Prehrajte video pre aktivovanie synchronizácie média", "app.network.connection.effective.slow": "Boli zaznemenané problémy so spojenÃm.", "app.network.connection.effective.slow.help": "Viac informácii", - "app.externalVideo.noteLabel": "Poznámka: Zdieľané externé videá sa neobjavia na nahrávke. YouTube, Vimeo, Instructure Media, Twitch a Daily Motion URL odkazy sú podporované.", + "app.externalVideo.noteLabel": "Poznámka: Zdieľané externé videá sa neobjavia na nahrávke. YouTube, Vimeo, Instructure Media, Twitch, Dailymotion a URL odkazy na médiá (napr. https://example.com/xy.mp4) sú podporované.", "app.actionsBar.actionsDropdown.shareExternalVideo": "ZdieľaÅ¥ externé video", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Koniec zdieľania externého videa", "app.iOSWarning.label": "ProsÃm zvážte upgrade na iOS 12.2 alebo vyššÃ", diff --git a/bigbluebutton-html5/private/locales/sl.json b/bigbluebutton-html5/private/locales/sl.json index 478b07989eac67e4e1658fbd24951bc9a28710f8..65e7ef854da2081547ec0f38976a6b2a6420c00b 100644 --- a/bigbluebutton-html5/private/locales/sl.json +++ b/bigbluebutton-html5/private/locales/sl.json @@ -18,6 +18,7 @@ "app.chat.dropdown.save": "Shrani", "app.chat.label": "Klepet", "app.chat.offline": "Brez povezave", + "app.chat.pollResult": "Rezultati ankete", "app.chat.emptyLogLabel": "Dnevnik klepeta je prazen", "app.chat.clearPublicChatMessage": "Zgodovino javnega klepeta je moderator poÄistil", "app.chat.multi.typing": "VeÄ udeležencev vpisuje besedilo", @@ -50,10 +51,10 @@ "app.note.title": "Skupne beležke", "app.note.label": "Beležka", "app.note.hideNoteLabel": "Skrij beležko", + "app.note.tipLabel": "Pritisnite tipko Esc za prikaz orodne vrstice urejevalnika", "app.user.activityCheck": "Preverjanje dejavnosti udeleženca", "app.user.activityCheck.label": "Preveri, ali je udeleženec Å¡e vedno prisoten ({0})", "app.user.activityCheck.check": "Preveri", - "app.note.tipLabel": "Pritisnite tipko Esc za prikaz orodne vrstice urejevalnika", "app.userList.usersTitle": "Udeleženci", "app.userList.participantsTitle": "Udeleženci", "app.userList.messagesTitle": "SporoÄila", @@ -95,6 +96,8 @@ "app.userList.userOptions.unmuteAllDesc": "Povrne zvok za vse udeležence", "app.userList.userOptions.lockViewersLabel": "Uporaba možnosti", "app.userList.userOptions.lockViewersDesc": "Zakleni izbrane možnosti za udeležence sestanka", + "app.userList.userOptions.connectionStatusLabel": "Stanje povezave", + "app.userList.userOptions.connectionStatusDesc": "Pokaži stanje povezave uporabnika", "app.userList.userOptions.disableCam": "Spletne kamere opazovalcev so onemogoÄene", "app.userList.userOptions.disableMic": "Mikrofoni opazovalcev so onemogoÄeni", "app.userList.userOptions.disablePrivChat": "Zasebni klepet je onemogoÄen", @@ -126,10 +129,13 @@ "app.meeting.meetingTimeRemaining": "Preostali Äas sreÄanja: {0}", "app.meeting.meetingTimeHasEnded": "ÄŒas je potekel. SreÄanje bo kmalu konÄano.", "app.meeting.endedMessage": "Stran bo preusmerjena na zaÄetni zaslon", - "app.meeting.alertMeetingEndsUnderOneMinute": "Sestanek bo v naslednji minuti konÄan.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "LoÄene skupine Äez minuto zaprte", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "Sestanek bo samodejno konÄan v eni minuti.", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "Sestanek bo samodejno konÄan v {0} minutah.", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "Skupine bodo razpuÅ¡Äene v {0} minutah.", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "Skupine bodo razpuÅ¡Äene v eni minuti.", "app.presentation.hide": "Skrij predstavitev", "app.presentation.notificationLabel": "Trenutna predstavitev", + "app.presentation.downloadLabel": "Prejmi", "app.presentation.slideContent": "Vsebina strani", "app.presentation.startSlideContent": "ZaÄetek vsebine strani", "app.presentation.endSlideContent": "Konec vsebine strani", @@ -174,6 +180,7 @@ "app.presentationUploder.rejectedError": "Izbrane datoteke so zavrnjene. Preverite podprtost vrst datotek.", "app.presentationUploder.upload.progress": "Poteka poÅ¡iljanje ({0} %)", "app.presentationUploder.upload.413": "Datoteka je prevelika. Razdelite jo na veÄ manjÅ¡ih.", + "app.presentationUploder.genericError": "Ojoj, priÅ¡lo je do napake.", "app.presentationUploder.upload.408": "Zahteva poÅ¡iljanja je Äasovno potekla.", "app.presentationUploder.upload.404": "404: neveljaven žeton poÅ¡iljanja", "app.presentationUploder.upload.401": "Zahteva po poÅ¡iljanju predstavitve je spodletela.", @@ -195,6 +202,13 @@ "app.presentationUploder.tableHeading.filename": "Ime datoteke", "app.presentationUploder.tableHeading.options": "Možnosti", "app.presentationUploder.tableHeading.status": "Stanje", + "app.presentationUploder.uploading": "Poteka poÅ¡iljanje {0} {1}", + "app.presentationUploder.uploadStatus": "{0} od {1} konÄanih", + "app.presentationUploder.completed": "{0} konÄanih", + "app.presentationUploder.item" : "predmet", + "app.presentationUploder.itemPlural" : "predmeti", + "app.presentationUploder.clearErrors": "PoÄisti napake", + "app.presentationUploder.clearErrorsDesc": "PoÄisti spodletela poÅ¡iljanja predstavitve", "app.poll.pollPaneTitle": "Ankete", "app.poll.quickPollTitle": "Hitra anketa", "app.poll.hidePollDesc": "Skrije okno menija ankete", @@ -240,6 +254,7 @@ "app.connectingMessage": "Poteka povezovanje ...", "app.waitingMessage": "Povezava je prekinjena. Poskus ponovnega povezovanja bo izveden Äez {0} sekund ...", "app.retryNow": "Poskusi takoj", + "app.muteWarning.label": "Kliknite {0} za povrnitev glasnosti.", "app.navBar.settingsDropdown.optionsLabel": "Možnosti", "app.navBar.settingsDropdown.fullscreenLabel": "Prikaži v celozaslonskem naÄinu", "app.navBar.settingsDropdown.settingsLabel": "Nastavitve", @@ -267,7 +282,7 @@ "app.leaveConfirmation.confirmLabel": "Zapusti", "app.leaveConfirmation.confirmDesc": "Odjavi od sestanka", "app.endMeeting.title": "KonÄaj sestanek", - "app.endMeeting.description": "Ali ste prepriÄani, da želite konÄati predstavitev?", + "app.endMeeting.description": "Ali ste prepriÄani, da želite konÄati sestanek za vse udeležence (povezava bo za vse prekinjena)", "app.endMeeting.yesLabel": "Da", "app.endMeeting.noLabel": "Ne", "app.about.title": "O programu", @@ -288,10 +303,6 @@ "app.screenshare.screenShareLabel" : "Souporaba zaslona", "app.submenu.application.applicationSectionTitle": "Program", "app.submenu.application.animationsLabel": "Animacije", - "app.submenu.application.audioAlertLabel": "ZvoÄna obvestila za klepet", - "app.submenu.application.pushAlertLabel": "Pojavna obvestila za klepet", - "app.submenu.application.userJoinAudioAlertLabel": "ZvoÄna obvestila ob prijavi udeleženca", - "app.submenu.application.userJoinPushAlertLabel": "Pojavna obvestila ob prijavi udeleženca", "app.submenu.application.fontSizeControlLabel": "Velikost pisave", "app.submenu.application.increaseFontBtnLabel": "PoveÄaj velikost pisave vmesnika", "app.submenu.application.decreaseFontBtnLabel": "ZmanjÅ¡aj velikost pisave vmesnika", @@ -299,6 +310,12 @@ "app.submenu.application.languageLabel": "Jezik programa", "app.submenu.application.languageOptionLabel": "Izbor jezika", "app.submenu.application.noLocaleOptionLabel": "Ni nameÅ¡Äenih jezikov", + "app.submenu.notification.SectionTitle": "Obvestila", + "app.submenu.notification.Desc": "DoloÄite, kako in o Äem želite biti obveÅ¡Äeni.", + "app.submenu.notification.audioAlertLabel": "ZvoÄna obvestila", + "app.submenu.notification.pushAlertLabel": "Pojavna obvestila", + "app.submenu.notification.messagesLabel": "SporoÄilo klepeta", + "app.submenu.notification.userJoinLabel": "Pridruženje uporabnika", "app.submenu.audio.micSourceLabel": "Vir mikrofona", "app.submenu.audio.speakerSourceLabel": "Uporabljeni zvoÄniki", "app.submenu.audio.streamVolumeLabel": "Glasnost zvokovnega pretoka", @@ -322,6 +339,10 @@ "app.settings.dataSavingTab.screenShare": "OmogoÄi souporabo namizja", "app.settings.dataSavingTab.description": "Obremenitev povezav med udeleženci je mogoÄe zmanjÅ¡ati s prilagajanjem prikazovanja.", "app.settings.save-notification.label": "Nastavitve so shranjene", + "app.statusNotifier.lowerHands": "Spusti roke", + "app.statusNotifier.raisedHandsTitle": "Dvignjene roke", + "app.statusNotifier.raisedHandDesc": "Dvignjene roke: {0}", + "app.statusNotifier.and": "in", "app.switch.onLabel": "I", "app.switch.offLabel": "O", "app.talkingIndicator.ariaMuteDesc" : "Izberite za utiÅ¡anje udeleženca", @@ -486,6 +507,8 @@ "app.userList.guest.pendingGuestUsers": "ÄŒakajoÄi gosti: {0}", "app.userList.guest.pendingGuestAlert": "Prijava Äaka na vaÅ¡o odobritev.", "app.userList.guest.rememberChoice": "Zapomni si izbiro", + "app.userList.guest.acceptLabel": "Sprejmi", + "app.userList.guest.denyLabel": "Zavrni", "app.user-info.title": "Pregled mape", "app.toast.breakoutRoomEnded": "LoÄene skupine so razpuÅ¡Äene. Pridružite se z zvokom.", "app.toast.chat.public": "Novo javno sporoÄilo klepeta", @@ -500,6 +523,7 @@ "app.notification.recordingPaused": "Predstavitev se ne snema veÄ", "app.notification.recordingAriaLabel": "ÄŒas snemanja", "app.notification.userJoinPushAlert": "{0} se je pridružil predstavitvi", + "app.submenu.notification.raiseHandLabel": "Dvigni roko", "app.shortcut-help.title": "Tipkovne bližnjice", "app.shortcut-help.accessKeyNotAvailable": "KljuÄ za dostop ni na voljo", "app.shortcut-help.comboLabel": "Tipke", @@ -533,6 +557,12 @@ "app.lock-viewers.button.cancel": "PrekliÄi", "app.lock-viewers.locked": "Zaklenjeno", "app.lock-viewers.unlocked": "Odklenjeno", + "app.connection-status.ariaTitle": "Okno stanja povezave", + "app.connection-status.title": "Stanje povezave", + "app.connection-status.description": "Pokaži stanje povezave uporabnika", + "app.connection-status.empty": "Trenutno Å¡e ni zaznanih težav s povezavo", + "app.connection-status.more": "veÄ", + "app.connection-status.offline": "brez povezave", "app.recording.startTitle": "ZaÄni snemanje", "app.recording.stopTitle": "Premor snemanja", "app.recording.resumeTitle": "Nadaljuj snemanje", @@ -540,10 +570,16 @@ "app.recording.stopDescription": "Ali ste prepriÄani, da želite zaustaviti snemanje. Kadarkoli lahko nadaljujete s ponovnim pritiskom na gumb.", "app.videoPreview.cameraLabel": "Kamera", "app.videoPreview.profileLabel": "Kakovost", + "app.videoPreview.quality.low": "Nizko", + "app.videoPreview.quality.medium": "Srednje", + "app.videoPreview.quality.high": "Visoko", "app.videoPreview.cancelLabel": "PrekliÄi", "app.videoPreview.closeLabel": "Zapri", "app.videoPreview.findingWebcamsLabel": "Iskanje spletnih kamer", "app.videoPreview.startSharingLabel": "ZaÄni predstavitev", + "app.videoPreview.stopSharingLabel": "Zaustavi prikazovanje", + "app.videoPreview.stopSharingAllLabel": "Zaustavi vse", + "app.videoPreview.sharedCameraLabel": "Ta kamera je že v uporabi", "app.videoPreview.webcamOptionLabel": "Izbor spletne kamere", "app.videoPreview.webcamPreviewLabel": "Predogled spletne kamere", "app.videoPreview.webcamSettingsTitle": "Nastavitve spletne kamere", @@ -573,19 +609,8 @@ "app.video.videoMenuDesc": "Odpre spustno polje menija videa", "app.video.chromeExtensionError": "Namestiti je treba", "app.video.chromeExtensionErrorLink": "razÅ¡iritev za Chrome", - "app.video.stats.title": "Statistika povezave", - "app.video.stats.packetsReceived": "Prejeti paketi", - "app.video.stats.packetsSent": "Poslani paketi", - "app.video.stats.packetsLost": "Izgubljeni paketi", - "app.video.stats.bitrate": "Bitna hitrost", - "app.video.stats.lostPercentage": "Skupni odstotek izgubljenih paketov", - "app.video.stats.lostRecentPercentage": "Nedavni odstotek izgubljenih paketov", - "app.video.stats.dimensions": "Dimenzije", - "app.video.stats.codec": "Kodek", - "app.video.stats.decodeDelay": "ÄŒasovni zamik odkodiranja", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Uporaba kodirnika", - "app.video.stats.currentDelay": "Trenutni Äasovni zamik", + "app.video.pagination.prevPage": "Predhodni videi", + "app.video.pagination.nextPage": "Naslednji videi", "app.fullscreenButton.label": "Postavite {0} v celozaslonski naÄin", "app.deskshare.iceConnectionStateError": "Povezava za souporabo zaslona je spodletela (Napaka ICE 1108)", "app.sfu.mediaServerConnectionError2000": "Ni se mogoÄe povezati s predstavnim strežnikom (napaka 2000)", @@ -598,7 +623,8 @@ "app.sfu.invalidSdp2202":"Odjemalec je ustvaril neveljavno zahtevo predstavne vsebine (napaka SDP 2202)", "app.sfu.noAvailableCodec2203": "Na strežniku ni nameÅ¡Äenega ustreznega kodeka (napaka 2203)", "app.meeting.endNotification.ok.label": "V redu", - "app.whiteboard.annotations.poll": "Rezultati ankete so objavljeni", + "app.whiteboard.annotations.poll": "Objavljeni so rezultati ankete", + "app.whiteboard.annotations.pollResult": "Rezultati ankete", "app.whiteboard.toolbar.tools": "Orodja", "app.whiteboard.toolbar.tools.hand": "Prijem", "app.whiteboard.toolbar.tools.pencil": "SvinÄnik", @@ -679,7 +705,7 @@ "app.externalVideo.autoPlayWarning": "ZaÄnite s predvajanjem videa za usklajevanje predstavne vsebine", "app.network.connection.effective.slow": "Zaznane so težave s povezljivostjo.", "app.network.connection.effective.slow.help": "VeÄ podrobnosti", - "app.externalVideo.noteLabel": "Opomba: zunanjih predvajanih videoposnetkov snemanje ne zajame. Podprto je sicer predvajanje posnetkov YouTube, Vimeo, Instructure Media, Twitch in Daily Motion.", + "app.externalVideo.noteLabel": "Opomba: snemanje ne zajame zunanjih predvajanih videoposnetkov. Podprto je sicer predvajanje posnetkov YouTube, Vimeo, Instructure Media, Twitch in DailyMotion in naslovi URL predstavnih datotek (na primer: https://domena.si/posnetek.mp4)", "app.actionsBar.actionsDropdown.shareExternalVideo": "Pokaži zunanji video posnetek", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Prekini prikazovanje zunanjega video posnetka", "app.iOSWarning.label": "Posodobite sistem na razliÄico iOS 12.2 ali viÅ¡jo", diff --git a/bigbluebutton-html5/private/locales/sr.json b/bigbluebutton-html5/private/locales/sr.json index 5079e2f9011b4c29d69ca62aa22d700de63d3193..4e1406f44f63013d0e408ab18609ce4b65cb3f45 100644 --- a/bigbluebutton-html5/private/locales/sr.json +++ b/bigbluebutton-html5/private/locales/sr.json @@ -1,13 +1,13 @@ { - "app.home.greeting": "VaÅ¡a prezentacija uskoro poÄinje ...", + "app.home.greeting": "VaÅ¡e predavanje uskoro poÄinje ...", "app.chat.submitLabel": "PoÅ¡aljite poruku", "app.chat.errorMaxMessageLength": "Poruka je predugaÄka za {0} karaktera", "app.chat.disconnected": "Niste povezani - poruke ne mogu biti poslate", "app.chat.locked": "Opcija komunikacije sa uÄesnicima trenutno nije dozvoljena - poruke ne mogu biti poslate", "app.chat.inputLabel": "UnoÅ¡enje poruke za komunikaciju sa uÄesnicima {0}", - "app.chat.inputPlaceholder": "PoÅ¡aljite poruku korisniku {0}", - "app.chat.titlePublic": "Javna komunikacija sa uÄesnicima", - "app.chat.titlePrivate": "Privatni razgovor sa korisnikom {0}", + "app.chat.inputPlaceholder": "PoÅ¡aljite poruku {0}", + "app.chat.titlePublic": "PriÄaonica", + "app.chat.titlePrivate": "Privatni razgovor sa {0}", "app.chat.partnerDisconnected": "{0} je napustio/la predavanje", "app.chat.closeChatLabel": "Zatvorite {0}", "app.chat.hideChatLabel": "Sakrijte {0}", @@ -18,9 +18,10 @@ "app.chat.dropdown.save": "SaÄuvajte", "app.chat.label": "Komunikacija izmeÄ‘u uÄesnika", "app.chat.offline": "Odsutan", - "app.chat.emptyLogLabel": "Dnevnik komunikacije je prazan", - "app.chat.clearPublicChatMessage": "Zapis javne komunikacije izmeÄ‘u uÄesnika je izbrisan od strane moderatora", - "app.chat.multi.typing": "ViÅ¡e polaznika kuca", + "app.chat.pollResult": "Rezultati ankete", + "app.chat.emptyLogLabel": "Zapis ćaskanja je prazan", + "app.chat.clearPublicChatMessage": "Arhiva priÄaonice je izbrisana od strane moderatora", + "app.chat.multi.typing": "ViÅ¡e uÄesnika kuca", "app.chat.one.typing": "{0} kuca", "app.chat.two.typing": "{0} i {1} kucaju", "app.captions.label": "Ispisi", @@ -39,7 +40,7 @@ "app.captions.menu.previewLabel": "Pregled", "app.captions.menu.cancelLabel": "Otkažite", "app.captions.pad.hide": "Sklonite titlove", - "app.captions.pad.tip": "Pritisnite dugme \"Esc\" kako biste uveliÄali meni za editovanje", + "app.captions.pad.tip": "Pritisnite dugme 'Esc' kako biste uveliÄali meni za editovanje", "app.captions.pad.ownership": "Preuzmite", "app.captions.pad.ownershipTooltip": "Biće vam dodeljeno vlasniÅ¡tvo nad {0} ispisa", "app.captions.pad.interimResult": "Trenutni rezultati", @@ -47,70 +48,77 @@ "app.captions.pad.dictationStop": "Zaustavite diktiranje", "app.captions.pad.dictationOnDesc": "UkljuÄite prepoznavanje govora", "app.captions.pad.dictationOffDesc": "IskljuÄite prepoznavanje govora", - "app.note.title": "BeleÅ¡ke za sve uÄesnike", + "app.note.title": "BeleÅ¡ke za uÄesnike", "app.note.label": "BeleÅ¡ka", "app.note.hideNoteLabel": "Sklonite beleÅ¡ku", - "app.user.activityCheck": "Provera aktivnosti polaznika", - "app.user.activityCheck.label": "Proverite da li je polaznik joÅ¡ uvek na predavanju ({0})", + "app.note.tipLabel": "Pritisnite dugme 'Esc' kako biste uveliÄali meni za editovanje", + "app.user.activityCheck": "Provera aktivnosti uÄesnika", + "app.user.activityCheck.label": "Proverite da li je uÄesnik joÅ¡ uvek na predavanju ({0})", "app.user.activityCheck.check": "Provera", - "app.note.tipLabel": "Pritisnite dugme \"Esc\" kako biste uveliÄali meni za editovanje", - "app.userList.usersTitle": "Polaznici", + "app.userList.usersTitle": "UÄesnici", "app.userList.participantsTitle": "UÄesnici", "app.userList.messagesTitle": "Poruke", "app.userList.notesTitle": "BeleÅ¡ke", - "app.userList.notesListItem.unreadContent": "U sekciji \"BeleÅ¡ke za sve uÄesnike\" se nalazi novi sadržaj", + "app.userList.notesListItem.unreadContent": "U sekciji \"BeleÅ¡ke za uÄesnike\" se nalazi novi sadržaj", "app.userList.captionsTitle": "Ispisi", "app.userList.presenter": "PredavaÄ", "app.userList.you": "Vi", "app.userList.locked": "ZakljuÄano", - "app.userList.label": "Lista polaznika", + "app.userList.byModerator": "od strane (Moderator)", + "app.userList.label": "Lista uÄesnika", "app.userList.toggleCompactView.label": "UkljuÄite/iskljuÄite kompaktni prikaz", "app.userList.guest": "Gost", "app.userList.menuTitleContext": "Raspoložive opcije", "app.userList.chatListItem.unreadSingular": "{0} Nova poruka", "app.userList.chatListItem.unreadPlural": "{0} Nove poruke", - "app.userList.menu.chat.label": "ZapoÄnite privatni razgovor", + "app.userList.menu.chat.label": "ZapoÄnite privatno dopisivanje", "app.userList.menu.clearStatus.label": "IzbriÅ¡ite status", - "app.userList.menu.removeUser.label": "Uklonite polaznika", - "app.userList.menu.muteUserAudio.label": "Ukinite glas polaznika", - "app.userList.menu.unmuteUserAudio.label": "Omogućite glas polaznika", + "app.userList.menu.removeUser.label": "Uklonite uÄesnika", + "app.userList.menu.removeConfirmation.label": "Uklonite uÄesnika ({0})", + "app.userlist.menu.removeConfirmation.desc": "SpreÄite ovog uÄesnika da ponovo pristupi sesiji", + "app.userList.menu.muteUserAudio.label": "Ukinite mikrofon uÄesniku", + "app.userList.menu.unmuteUserAudio.label": "Dozvolite mikrofon uÄesniku", "app.userList.userAriaLabel": "{0} {1} {2} Status {3}", "app.userList.menu.promoteUser.label": "Unapredite u ulogu moderatora", - "app.userList.menu.demoteUser.label": "Vratite u ulogu gledaoca", + "app.userList.menu.demoteUser.label": "Vratite u ulogu obiÄnog uÄesnika", "app.userList.menu.unlockUser.label": "OtkljuÄajte {0}", "app.userList.menu.lockUser.label": "ZakljuÄajte {0}", "app.userList.menu.directoryLookup.label": "Pretraga po direktorijumu", "app.userList.menu.makePresenter.label": "Dodelite ulogu predavaÄa", - "app.userList.userOptions.manageUsersLabel": "Upravljanje polaznicima", - "app.userList.userOptions.muteAllLabel": "Ukinite glas svim polaznicima", - "app.userList.userOptions.muteAllDesc": "Ukinite glas svim polaznicima na predavanju", + "app.userList.userOptions.manageUsersLabel": "Upravljanje uÄesnicima", + "app.userList.userOptions.muteAllLabel": "Ukinite mikrofon svim uÄesnicima", + "app.userList.userOptions.muteAllDesc": "Ukinite mikrofon svim uÄesnicima na predavanju", "app.userList.userOptions.clearAllLabel": "IzbriÅ¡ite sve statusne ikone", - "app.userList.userOptions.clearAllDesc": "IzbriÅ¡ite sve statusne ikone polaznika", - "app.userList.userOptions.muteAllExceptPresenterLabel": "Ukinite glas svim polaznicima osim predavaÄu", - "app.userList.userOptions.muteAllExceptPresenterDesc": "Ukida glas svim polaznicima na predavanju osim predavaÄu", + "app.userList.userOptions.clearAllDesc": "IzbriÅ¡ite sve statusne ikone uÄesnika", + "app.userList.userOptions.muteAllExceptPresenterLabel": "Ukinite mikrofon svim uÄesnicima osim predavaÄu", + "app.userList.userOptions.muteAllExceptPresenterDesc": "Ukida mikrofon svim uÄesnicima na predavanju osim predavaÄu", "app.userList.userOptions.unmuteAllLabel": "IskljuÄite ukidanje glasa na predavanju", "app.userList.userOptions.unmuteAllDesc": "IskljuÄite ukidanje glasa na predavanju", - "app.userList.userOptions.lockViewersLabel": "ZakljuÄajte gledaoce", + "app.userList.userOptions.lockViewersLabel": "ZakljuÄajte opcije uÄesnicima", "app.userList.userOptions.lockViewersDesc": "ZakljuÄajte odreÄ‘ene funkcionalnosti za uÄesnike predavanja", - "app.userList.userOptions.disableCam": "Veb kamere gledalaca su onemogućene za korišćenje", - "app.userList.userOptions.disableMic": "Mikrofoni gledalaca su onemogućeni za korišćenje", + "app.userList.userOptions.connectionStatusLabel": "Status povezivanja", + "app.userList.userOptions.connectionStatusDesc": "Pogledajte status povezivanja uÄesnika", + "app.userList.userOptions.disableCam": "Veb kamere uÄesnika nisu omogućene", + "app.userList.userOptions.disableMic": "Mikrofoni uÄesnika nisu omogućeni", "app.userList.userOptions.disablePrivChat": "Privatni razgovor je onemogućen", - "app.userList.userOptions.disablePubChat": "Javna komunikacija izmeÄ‘u uÄesnika je onemogućena", - "app.userList.userOptions.disableNote": "Sekcija \"BeleÅ¡ke za sve uÄesnike\" je sada zakljuÄana", - "app.userList.userOptions.hideUserList": "Lista polaznika je sada sakrivena za gledaoce", - "app.userList.userOptions.webcamsOnlyForModerator": "Samo moderatori mogu videti veb kamere gledalaca (usled zakljuÄanih podeÅ¡avanja)", - "app.userList.content.participants.options.clearedStatus": "Izbrisani su statusi svih polaznika", - "app.userList.userOptions.enableCam": "Veb kamere gledalaca su omogućene za korišćenje", - "app.userList.userOptions.enableMic": "Mikrofoni gledalaca su omogućeni za korišćenje", + "app.userList.userOptions.disablePubChat": "PriÄaonica za uÄesnike je onemogućena", + "app.userList.userOptions.disableNote": "Sekcija \"BeleÅ¡ke za uÄesnike\" je sada zakljuÄana", + "app.userList.userOptions.hideUserList": "Lista uÄesnika je sada sakrivena", + "app.userList.userOptions.webcamsOnlyForModerator": "Samo moderatori mogu videti veb kamere uÄesnika (usled zakljuÄanih podeÅ¡avanja)", + "app.userList.content.participants.options.clearedStatus": "Izbrisane su statusne ikone svih uÄesnika", + "app.userList.userOptions.enableCam": "Veb kamere uÄesnika su omogućene", + "app.userList.userOptions.enableMic": "Mikrofoni uÄesnika su omogućeni", "app.userList.userOptions.enablePrivChat": "Privatni razgovor je omogućen", - "app.userList.userOptions.enablePubChat": "Javna komunikacija izmeÄ‘u uÄesnika je omogućena", - "app.userList.userOptions.enableNote": "Sekcija \"BeleÅ¡ke za sve uÄesnike\" je sada dostupna", - "app.userList.userOptions.showUserList": "Lista polaznika se sada prikazuje gledaocima", + "app.userList.userOptions.enablePubChat": "PriÄaonica za uÄesnike je omogućena", + "app.userList.userOptions.enableNote": "Sekcija \"BeleÅ¡ke za uÄesnike\" je sada dostupna", + "app.userList.userOptions.showUserList": "Lista uÄesnika je sada prikazana", "app.userList.userOptions.enableOnlyModeratorWebcam": "Možete aktivirati svoju veb kameru sada - svi će vas videti", "app.media.label": "Medij", "app.media.autoplayAlertDesc": "Dozvolite pristup", "app.media.screenshare.start": "Deljenje ekrana je zapoÄeto", "app.media.screenshare.end": "Deljenje ekrana je zavrÅ¡eno", + "app.media.screenshare.unavailable": "Deljenje ekrana nije moguće", + "app.media.screenshare.notSupported": "Ovaj pretraživaÄ ne omogućava deljenje ekrana.", "app.media.screenshare.autoplayBlockedDesc": "Da bismo vam prikazali ekran predavaÄa, treba nam vaÅ¡e odobrenje.", "app.media.screenshare.autoplayAllowLabel": "Pogledajte deljeni ekran", "app.screenshare.notAllowed": "GreÅ¡ka: Nije dato odobrenje za pristup ekranu.", @@ -121,10 +129,11 @@ "app.meeting.meetingTimeRemaining": "Preostalo vreme za predavanje: {0}", "app.meeting.meetingTimeHasEnded": "Vreme je isteklo. Predavanje će se uskoro zavrÅ¡iti", "app.meeting.endedMessage": "Bićete preusmereni na poÄetni ekran", - "app.meeting.alertMeetingEndsUnderOneMinute": "Predavanje se uskoro zavrÅ¡ava.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Odvojena soba se uskoro zatvara.", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "Predavanje se zavrÅ¡ava za jedan minut.", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "Predavanje se zavrÅ¡ava za {0} minuta.", "app.presentation.hide": "Sklonite prezentaciju", "app.presentation.notificationLabel": "Trenutna prezentacija", + "app.presentation.downloadLabel": "Preuzmite", "app.presentation.slideContent": "Sadržaj slajda", "app.presentation.startSlideContent": "PoÄetak sadržaja slajda", "app.presentation.endSlideContent": "ZavrÅ¡etak sadržaja slajda", @@ -154,7 +163,7 @@ "app.presentation.presentationToolbar.fitToPage": "Prilagodite veliÄini stranice", "app.presentation.presentationToolbar.goToSlide": "Slajd {0}", "app.presentationUploder.title": "Prezentacija", - "app.presentationUploder.message": "Kao predavaÄ, imate mogućnost da uvezete bilo koji office dokument ili PDF fajl. Za najbolje rezultate, preporuÄujemo PDF fajl. Prilikom uvoza, oznaÄite fajl ua uvoz u kružnom polju sa desne strane.", + "app.presentationUploder.message": "Kao predavaÄ, imate mogućnost da uvezete Microsoft Office dokument ili PDF fajl (preporuÄeno). Ikonama sa desne strane možete dodatno urediti prenos - koji fajl će biti prebaÄen, da li će biti omogućen uÄesnicima za prenos, koji fajl će biti obrisan.", "app.presentationUploder.uploadLabel": "Uvoz", "app.presentationUploder.confirmLabel": "Potvrdite", "app.presentationUploder.confirmDesc": "SaÄuvajte izmene i zapoÄnite prezentaciju", @@ -169,13 +178,18 @@ "app.presentationUploder.rejectedError": "Odabrani fajl/fajlovi su odbijeni. Proverite vrstu fajla/fajlova.", "app.presentationUploder.upload.progress": "Uvoz ({0}%)", "app.presentationUploder.upload.413": "Fajl je prevelik. Podelite u viÅ¡e fajlova.", + "app.presentationUploder.genericError": "Ups, neÅ¡to nije u redu...", "app.presentationUploder.conversion.conversionProcessingSlides": "Procesiranje stranice {0} od {1}", "app.presentationUploder.conversion.genericConversionStatus": "Konverzija fajla ...", "app.presentationUploder.conversion.generatingThumbnail": "Kreiranje ikona ...", "app.presentationUploder.conversion.generatedSlides": "Kreiranje slajdova ...", "app.presentationUploder.conversion.generatingSvg": "Kreiranje SVG slika ...", + "app.presentationUploder.conversion.pageCountExceeded": "Dozvoljeni broj stranica je premaÅ¡en. Podelite fajl na viÅ¡e manjih fajlova.", + "app.presentationUploder.conversion.officeDocConversionInvalid": "GreÅ¡ka u procesiranju Office dokumenta. Odaberite PDF dokument umesto ovog dokumenta.", + "app.presentationUploder.conversion.officeDocConversionFailed": "GreÅ¡ka u procesiranju Office dokumenta. Odaberite PDF dokument umesto ovog dokumenta.", "app.presentationUploder.conversion.pdfHasBigPage": "Nismo uspeli da konvertujemo PDF fajl, probajte da ga optimizujete", "app.presentationUploder.conversion.timeout": "Ups, konverzija traje predugo", + "app.presentationUploder.conversion.pageCountFailed": "GreÅ¡ka u odreÄ‘ivanju koliko dokument sadrži stranica.", "app.presentationUploder.isDownloadableLabel": "Onemogućite preuzimanje prezentacije", "app.presentationUploder.isNotDownloadableLabel": "Dozvolite preuzimanje prezentacije", "app.presentationUploder.removePresentationLabel": "Uklonite prezentaciju", @@ -183,20 +197,25 @@ "app.presentationUploder.tableHeading.filename": "Ime fajla", "app.presentationUploder.tableHeading.options": "Opcije", "app.presentationUploder.tableHeading.status": "Status", + "app.presentationUploder.uploading": "Prebacivanje {0} {1}", + "app.presentationUploder.uploadStatus": "{0} of {1} dokumenata su prebaÄeni", + "app.presentationUploder.completed": "{0} dokumenata je zavrÅ¡eno", + "app.presentationUploder.clearErrors": "ObriÅ¡i greÅ¡ke", + "app.presentationUploder.clearErrorsDesc": "ObriÅ¡i prezentacije koje su neuspeÅ¡no prebaÄene", "app.poll.pollPaneTitle": "Anketiranje", "app.poll.quickPollTitle": "Brza anketa", "app.poll.hidePollDesc": "Sklonite meni ankete", - "app.poll.customPollInstruction": "Kako biste kreirali posebnu anketu, odaberite dugme ispod i unesite opcije.", - "app.poll.quickPollInstruction": "Odaberite opciju ispod kako biste zapoÄeli anketu.", + "app.poll.customPollInstruction": "Za anketu sa specifiÄnim odgovorima koje ćete sami kreirati kliknite na dugme ispod.", + "app.poll.quickPollInstruction": "Odaberite tipove odgovora kako biste zapoÄeli anketu.", "app.poll.customPollLabel": "Posebna anketa", "app.poll.startCustomLabel": "ZapoÄnite posebnu anketu", - "app.poll.activePollInstruction": "Ostavite ovaj panel otvorenim kako biste videli rezultate ankete u realnom vremenu. Kada ste spremni, odaberite \"Objavite rezultate ankete\" kako biste objavili rezultate i zavrÅ¡ili anketu.", + "app.poll.activePollInstruction": "Ostavite ovaj panel otvorenim kako biste videli rezultate ankete u realnom vremenu. Kada ste spremni, odaberite 'Objavite rezultate ankete' kako biste objavili rezultate i zavrÅ¡ili anketu.", "app.poll.publishLabel": "Objavite rezultate ankete", "app.poll.backLabel": "Povratak na opcije ankete", "app.poll.closeLabel": "Zatvorite", "app.poll.waitingLabel": "ÄŒekanje na odgovore ({0}/{1})", "app.poll.ariaInputCount": "Opcija posebne ankete {0} od {1}", - "app.poll.customPlaceholder": "Dodajte opciju ankete", + "app.poll.customPlaceholder": "Dodajte odgovor za pitanje", "app.poll.noPresentationSelected": "Ni jedna prezentacija nije odabrana! Odaberite jednu.", "app.poll.clickHereToSelect": "Kliknite ovde kako biste odabrali", "app.poll.t": "TaÄno", @@ -218,7 +237,7 @@ "app.poll.answer.c": "C", "app.poll.answer.d": "D", "app.poll.answer.e": "E", - "app.poll.liveResult.usersTitle": "Polaznici", + "app.poll.liveResult.usersTitle": "UÄesnici", "app.poll.liveResult.responsesTitle": "Odgovor", "app.polling.pollingTitle": "Opcije ankete", "app.polling.pollAnswerLabel": "Odgovor ankete {0}", @@ -228,6 +247,7 @@ "app.connectingMessage": "Povezivanje ...", "app.waitingMessage": "Veza je prekinuta. PokuÅ¡aj rekonekcije za {0} sekundi ...", "app.retryNow": "PokuÅ¡ajte ponovo sada", + "app.muteWarning.label": "Kliknite na {0} da biste ukljuÄili sebi mikrofon.", "app.navBar.settingsDropdown.optionsLabel": "Opcije", "app.navBar.settingsDropdown.fullscreenLabel": "Prikaz na celom ekranu", "app.navBar.settingsDropdown.settingsLabel": "PodeÅ¡avanja", @@ -242,11 +262,11 @@ "app.navBar.settingsDropdown.hotkeysLabel": "Skraćenice na tastaturi", "app.navBar.settingsDropdown.hotkeysDesc": "Lista raspoloživih skraćenica na tastaturi", "app.navBar.settingsDropdown.helpLabel": "Pomoć", - "app.navBar.settingsDropdown.helpDesc": "Usmerava polaznike ka video tutorijalima (otvara se u novom jeziÄku)", + "app.navBar.settingsDropdown.helpDesc": "Usmerava uÄesnike ka video tutorijalima (otvara se u novom jeziÄku)", "app.navBar.settingsDropdown.endMeetingDesc": "Prekida trenutno predavanje", "app.navBar.settingsDropdown.endMeetingLabel": "ZavrÅ¡i predavanje", - "app.navBar.userListToggleBtnLabel": "UkljuÄite/iskljuÄite listu polaznika", - "app.navBar.toggleUserList.ariaLabel": "UkljuÄite/iskljuÄite polaznike i poruke", + "app.navBar.userListToggleBtnLabel": "UkljuÄite/iskljuÄite listu uÄesnika", + "app.navBar.toggleUserList.ariaLabel": "UkljuÄite/iskljuÄite uÄesnike i poruke", "app.navBar.toggleUserList.newMessages": "sa notifikacijom za novu poruku", "app.navBar.recording": "Ova sesija se snima", "app.navBar.recording.on": "Snima se", @@ -255,7 +275,7 @@ "app.leaveConfirmation.confirmLabel": "Napustite", "app.leaveConfirmation.confirmDesc": "Odjavite se sa predavanja", "app.endMeeting.title": "ZavrÅ¡ite predavanje", - "app.endMeeting.description": "Da li ste sigurni da želite da zavrÅ¡ite ovu sesiju?", + "app.endMeeting.description": "Da li ste sigurni da želite da zavrÅ¡ite ovu sesiju za sve (svi uÄesnici će biti izbaÄeni)?", "app.endMeeting.yesLabel": "Da", "app.endMeeting.noLabel": "Ne", "app.about.title": "ViÅ¡e o", @@ -266,20 +286,16 @@ "app.about.dismissLabel": "Otkažite", "app.about.dismissDesc": "Zatvorite informacije o aplikaciji", "app.actionsBar.changeStatusLabel": "Promenite status", - "app.actionsBar.muteLabel": "Ukinite glas", - "app.actionsBar.unmuteLabel": "Omogućite glas", + "app.actionsBar.muteLabel": "Ukinite mikrofon", + "app.actionsBar.unmuteLabel": "Omogućite mikrofon", "app.actionsBar.camOffLabel": "Ugasite kameru", - "app.actionsBar.raiseLabel": "Podignite", + "app.actionsBar.raiseLabel": "Podignite ruku", "app.actionsBar.label": "Statusna linija za akcije", "app.actionsBar.actionsDropdown.restorePresentationLabel": "Povratite prezentaciju", "app.actionsBar.actionsDropdown.restorePresentationDesc": "Dugme za povraćaj prezentacije nakon Å¡to je ista zatvorena", "app.screenshare.screenShareLabel" : "Deljenje ekrana", "app.submenu.application.applicationSectionTitle": "Aplikacija", "app.submenu.application.animationsLabel": "Animacije", - "app.submenu.application.audioAlertLabel": "ZvuÄne notifikacije za komunikaciju izmeÄ‘u uÄesnika", - "app.submenu.application.pushAlertLabel": "IskaÄuće notifikacije za komunikaciju izmeÄ‘u uÄesnika", - "app.submenu.application.userJoinAudioAlertLabel": "ZvuÄne notifikacije za pridruživanje polaznika", - "app.submenu.application.userJoinPushAlertLabel": "IskaÄuće notifikacije za pridruživanje polaznika", "app.submenu.application.fontSizeControlLabel": "VeliÄina fonta", "app.submenu.application.increaseFontBtnLabel": "Povećajte veliÄinu fonta aplikacije", "app.submenu.application.decreaseFontBtnLabel": "Smanjite veliÄinu fonta aplikacije", @@ -287,14 +303,20 @@ "app.submenu.application.languageLabel": "Jezik aplikacije", "app.submenu.application.languageOptionLabel": "Podesite jezik", "app.submenu.application.noLocaleOptionLabel": "Nema aktivnih lokala", + "app.submenu.notification.SectionTitle": "ObaveÅ¡tenja", + "app.submenu.notification.Desc": "Odredite za Å¡ta ćete i kako biti obaveÅ¡teni.", + "app.submenu.notification.audioAlertLabel": "ZvuÄna upozorenja", + "app.submenu.notification.pushAlertLabel": "Upozorenja uz iskaÄuće prozore", + "app.submenu.notification.messagesLabel": "Poruka", + "app.submenu.notification.userJoinLabel": "UÄesnik se prikljuÄio", "app.submenu.audio.micSourceLabel": "Izvor mikrofona", "app.submenu.audio.speakerSourceLabel": "Izvor zvuÄnika", "app.submenu.audio.streamVolumeLabel": "Glasnoća vaÅ¡eg audio emitovanja", "app.submenu.video.title": "Video", "app.submenu.video.videoSourceLabel": "Pogledajte izvor", "app.submenu.video.videoOptionLabel": "Odaberite izvor za gledanje", - "app.submenu.video.videoQualityLabel": "Kvalitet video sekcije", - "app.submenu.video.qualityOptionLabel": "Podesite kvalitet video sekcije", + "app.submenu.video.videoQualityLabel": "Kvalitet video klipa", + "app.submenu.video.qualityOptionLabel": "Podesite kvalitet video klipa", "app.submenu.video.participantsCamLabel": "Gledanje veb kamera uÄesnika", "app.settings.applicationTab.label": "Aplikacija", "app.settings.audioTab.label": "Audio", @@ -310,9 +332,13 @@ "app.settings.dataSavingTab.screenShare": "Omogućite deljenje sadržaja sa ekrana", "app.settings.dataSavingTab.description": "Kako bi se ograniÄio protok podataka, uredite Å¡ta se trenutno prikazuje na ekranu.", "app.settings.save-notification.label": "PodeÅ¡avanja su saÄuvana", + "app.statusNotifier.lowerHands": "Spustite ruke", + "app.statusNotifier.raisedHandsTitle": "Podignute ruke", + "app.statusNotifier.raisedHandDesc": "{0} je podiglo ruke", + "app.statusNotifier.and": "i", "app.switch.onLabel": "UkljuÄeno", "app.switch.offLabel": "IskljuÄeno", - "app.talkingIndicator.ariaMuteDesc" : "Odaberite da ukinete glas polazniku", + "app.talkingIndicator.ariaMuteDesc" : "Ukinite mikrofon uÄesniku", "app.talkingIndicator.isTalking" : "{0} govori", "app.talkingIndicator.wasTalking" : "{0} je prestao/la da govori", "app.actionsBar.actionsDropdown.actionsLabel": "Akcije", @@ -320,51 +346,53 @@ "app.actionsBar.actionsDropdown.initPollLabel": "ZapoÄnite anketu", "app.actionsBar.actionsDropdown.desktopShareLabel": "Podelite svoj ekran", "app.actionsBar.actionsDropdown.lockedDesktopShareLabel": "Deljenje ekrana je zakljuÄano", - "app.actionsBar.actionsDropdown.stopDesktopShareLabel": "Prestanite sa deljenjem svog ekrana", + "app.actionsBar.actionsDropdown.stopDesktopShareLabel": "Zaustavite deljenje svog ekrana", "app.actionsBar.actionsDropdown.presentationDesc": "Uvezite svoju prezentaciju", "app.actionsBar.actionsDropdown.initPollDesc": "ZapoÄnite anketu", - "app.actionsBar.actionsDropdown.desktopShareDesc": "Podelite prikaz svog ekrana sa drugima", - "app.actionsBar.actionsDropdown.stopDesktopShareDesc": "Zaustavite deljenje prikaza svog ekrana sa", + "app.actionsBar.actionsDropdown.desktopShareDesc": "Podelite prikaz svog ekrana", + "app.actionsBar.actionsDropdown.stopDesktopShareDesc": "Zaustavite deljenje prikaza svog ekrana", "app.actionsBar.actionsDropdown.pollBtnLabel": "ZapoÄnite anketu", "app.actionsBar.actionsDropdown.pollBtnDesc": "UkljuÄite/iskljuÄite panel za ankete", - "app.actionsBar.actionsDropdown.saveUserNames": "SaÄuvajte imena polaznika", + "app.actionsBar.actionsDropdown.saveUserNames": "SaÄuvajte imena uÄesnika", "app.actionsBar.actionsDropdown.createBreakoutRoom": "Kreirajte odvojene sobe", "app.actionsBar.actionsDropdown.createBreakoutRoomDesc": "Kreirajte odvojene sobe kako biste podelili uÄesnike predavanja", - "app.actionsBar.actionsDropdown.captionsLabel": "IspiÅ¡ite titlove", + "app.actionsBar.actionsDropdown.captionsLabel": "Kreirajte titlove", "app.actionsBar.actionsDropdown.captionsDesc": "UkljuÄite/iskljuÄite panel za ispise", "app.actionsBar.actionsDropdown.takePresenter": "Uzmite predavaÄa", - "app.actionsBar.actionsDropdown.takePresenterDesc": "Dodelite sebi ulogu novog predavaÄa", + "app.actionsBar.actionsDropdown.takePresenterDesc": "Dodelite sebi ulogu predavaÄa", "app.actionsBar.emojiMenu.statusTriggerLabel": "Postavite status", "app.actionsBar.emojiMenu.awayLabel": "Odsutan", - "app.actionsBar.emojiMenu.awayDesc": "Promenite svoj status na \"odsutan\"", + "app.actionsBar.emojiMenu.awayDesc": "Promenite svoj status na odsutan", "app.actionsBar.emojiMenu.raiseHandLabel": "Podignite", - "app.actionsBar.emojiMenu.raiseHandDesc": "Podignite ruku kako biste postavili pitanje", + "app.actionsBar.emojiMenu.raiseHandDesc": "Podignite ruku da postavite pitanje", "app.actionsBar.emojiMenu.neutralLabel": "NeodluÄan", - "app.actionsBar.emojiMenu.neutralDesc": "Promenite svoj status na \"neodluÄan\"", + "app.actionsBar.emojiMenu.neutralDesc": "Promenite svoj status na neodluÄan", "app.actionsBar.emojiMenu.confusedLabel": "Zbunjen", - "app.actionsBar.emojiMenu.confusedDesc": "Promenite svoj status na \"zbunjen\"", + "app.actionsBar.emojiMenu.confusedDesc": "Promenite svoj status na zbunjen", "app.actionsBar.emojiMenu.sadLabel": "Tužan", - "app.actionsBar.emojiMenu.sadDesc": "Promenite svoj status na \"tužan\"", + "app.actionsBar.emojiMenu.sadDesc": "Promenite svoj status na tužan", "app.actionsBar.emojiMenu.happyLabel": "Srećan", - "app.actionsBar.emojiMenu.happyDesc": "Promenite svoj status na \"srećan\"", + "app.actionsBar.emojiMenu.happyDesc": "Promenite svoj status na srećan", "app.actionsBar.emojiMenu.noneLabel": "Uklonite status", "app.actionsBar.emojiMenu.noneDesc": "Uklonite svoj status", "app.actionsBar.emojiMenu.applauseLabel": "TapÅ¡anje", - "app.actionsBar.emojiMenu.applauseDesc": "Promenite svoj status na \"tapÅ¡anje\"", - "app.actionsBar.emojiMenu.thumbsUpLabel": "PalÄevi gore", - "app.actionsBar.emojiMenu.thumbsUpDesc": "Promenite svoj status na \"palÄevi gore\"", - "app.actionsBar.emojiMenu.thumbsDownLabel": "PalÄevi dole", - "app.actionsBar.emojiMenu.thumbsDownDesc": "Promenite svoj status na \"palÄevi dole\"", + "app.actionsBar.emojiMenu.applauseDesc": "Promenite svoj status na tapÅ¡anje", + "app.actionsBar.emojiMenu.thumbsUpLabel": "Palac gore", + "app.actionsBar.emojiMenu.thumbsUpDesc": "Promenite svoj status na palac gore", + "app.actionsBar.emojiMenu.thumbsDownLabel": "Palac dole", + "app.actionsBar.emojiMenu.thumbsDownDesc": "Promenite svoj status na palac dole", "app.actionsBar.currentStatusDesc": "Trenutni status {0}", - "app.actionsBar.captions.start": "ZapoÄnite gledanje titlova", - "app.actionsBar.captions.stop": "Zaustavite gledanje titlova", - "app.audioNotification.audioFailedMessage": "NeuspeÅ¡no povezivanje zvuÄne veze", + "app.actionsBar.captions.start": "UkljuÄite titlove", + "app.actionsBar.captions.stop": "IskljuÄite titlove", + "app.audioNotification.audioFailedError1003": "Verzija pretraživaÄa nije podržana (greÅ¡ka 1003)", + "app.audioNotification.audioFailedError1005": "Poziv se neoÄekivano zavrÅ¡io (greÅ¡ka 1005)", + "app.audioNotification.audioFailedMessage": "NeuspeÅ¡no povezivanje zvuka", "app.audioNotification.mediaFailedMessage": "getUserMicMedia je zavrÅ¡en neuspeÅ¡no zbog toga Å¡to su samo sigurni ureÄ‘aji dozvoljeni", "app.audioNotification.closeLabel": "Zatvorite", - "app.audioNotificaion.reconnectingAsListenOnly": "Deljenje mikrofona je zakljuÄano za gledaoce, povezani ste na naÄin koji omogućava samo sluÅ¡anje", + "app.audioNotificaion.reconnectingAsListenOnly": "Mikrofoni nisu trenutno omogućeni, povezani ste na naÄin koji omogućava samo sluÅ¡anje", "app.breakoutJoinConfirmation.title": "Pridružite se odvojenoj sobi", "app.breakoutJoinConfirmation.message": "Da li želite da se prikljuÄite", - "app.breakoutJoinConfirmation.confirmDesc": "Pridružuje vas odvojenoj sobi", + "app.breakoutJoinConfirmation.confirmDesc": "vam se pridružio u odvojenoj sobi", "app.breakoutJoinConfirmation.dismissLabel": "Otkažite", "app.breakoutJoinConfirmation.dismissDesc": "Zatvara i odbija pristup odvojenoj sobi", "app.breakoutJoinConfirmation.freeJoinMessage": "Odaberite odvojenu sobu kojoj želite da se pridružite", @@ -373,8 +401,8 @@ "app.calculatingBreakoutTimeRemaining": "ProraÄunavanje preostalog vremena ...", "app.audioModal.ariaTitle": "PrikljuÄite se audio modulu", "app.audioModal.microphoneLabel": "Mikrofon", - "app.audioModal.listenOnlyLabel": "Modalitet \"samo sluÅ¡anje\"", - "app.audioModal.audioChoiceLabel": "Na koji naÄin biste želeli da pristupite predavanju iz zvuÄne perspektive?", + "app.audioModal.listenOnlyLabel": "Samo zvuÄnici/sluÅ¡alice", + "app.audioModal.audioChoiceLabel": "Kako želite da se prikljuÄite predavanju?", "app.audioModal.iOSBrowser": "Audio/video nije podržan", "app.audioModal.iOSErrorDescription": "U ovom trenutku, audio i video nisu podržani na Chrome-u za iOS.", "app.audioModal.iOSErrorRecommendation": "Za iOS, preporuÄujemo korišćenje Safari-ja.", @@ -385,10 +413,10 @@ "app.audioModal.no": "Ne", "app.audioModal.yes.arialabel" : "Zvuk se Äuje", "app.audioModal.no.arialabel" : "Zvuk se ne Äuje", - "app.audioModal.echoTestTitle": "Ovo je test zvuka. Izgovorite par reÄi. Da li ste Äuli zvuk?", + "app.audioModal.echoTestTitle": "Ovo je test zvuka. Izgovorite par reÄi i posluÅ¡ajte da li ste ih Äuli na vaÅ¡im zvuÄnicima ili sluÅ¡alicama?", "app.audioModal.settingsTitle": "Promenite podeÅ¡avanja zvuka", "app.audioModal.helpTitle": "Postoji problem sa vaÅ¡im multimedijalnim ureÄ‘ajima", - "app.audioModal.helpText": "Da li ste odobrili pristup vaÅ¡em mikrofonu? Obratite pažnju - kada pokuÅ¡ate da se pridružite zvukom, oÄekujte ekran koji traži dozvolu pristupa vaÅ¡em multimedijalnom ureÄ‘aju. Potrebno je da odobrite pristup. Ako ovo nije reÅ¡enje problema, pokuÅ¡ajte da podesite dozvolu pristupa mikrofonu u pretraživaÄu koji koristite.", + "app.audioModal.helpText": "Da li ste odobrili pristup vaÅ¡em mikrofonu? Obratite pažnju - kada pokuÅ¡ate da odobrite audio pridruživanje, oÄekujte ekran koji traži dozvolu pristupa vaÅ¡em multimedijalnom ureÄ‘aju. Potrebno je da odobrite pristup. Ako ovo nije reÅ¡enje problema, pokuÅ¡ajte da podesite dozvolu pristupa mikrofonu u pretraživaÄu koji koristite.", "app.audioModal.help.noSSL": "Ova stranica nije sigurna. Da bi pristup mikrofonu bio odobren, stranica mora biti zaÅ¡tićena HTTPS protokolom. Kontaktirajte administratora servera.", "app.audioModal.help.macNotAllowed": "Izgleda da podeÅ¡avanja na vaÅ¡em Mac raÄunaru blokiraju pristup mikrofonu. Otvorite System Preferences > Security & Privacy > Privacy > Microphone i uverite se da je pretraživaÄ koji koristite oznaÄen.", "app.audioModal.audioDialTitle": "Pridružite se koristeći svoj telefon", @@ -402,7 +430,7 @@ "app.audioModal.connecting": "Povezivanje", "app.audioModal.connectingEchoTest": "Povezivanje testa glasa", "app.audioManager.joinedAudio": "Pristupili ste audio predavanju", - "app.audioManager.joinedEcho": "Pristupili ste testu glasa", + "app.audioManager.joinedEcho": "Pristupili ste zvuÄnom testu", "app.audioManager.leftAudio": "Napustili ste audio predavanje", "app.audioManager.reconnectingAudio": "PokuÅ¡aj da se ponovo uspostavi zvuk", "app.audioManager.genericError": "GreÅ¡ka: DoÅ¡lo je do greÅ¡ke, molimo vas da pokuÅ¡ate ponovo", @@ -410,7 +438,7 @@ "app.audioManager.requestTimeout": "GreÅ¡ka: Isteklo je vreme zahteva", "app.audioManager.invalidTarget": "GreÅ¡ka: Poslat je zahtev ka nepostojećem objektu", "app.audioManager.mediaError": "GreÅ¡ka: Problem sa pristupom vaÅ¡im multimedijalnim ureÄ‘ajima", - "app.audio.joinAudio": "Pridružite se audio kanalu", + "app.audio.joinAudio": "UkljuÄite zvuk", "app.audio.leaveAudio": "Napustite audio kanal", "app.audio.enterSessionLabel": "Pridružite se sesiji", "app.audio.playSoundLabel": "Reprodukujte zvuk", @@ -427,11 +455,11 @@ "app.audio.permissionsOverlay.hint": "Potrebno je da dozvolite pristup svojim multimedijalnim ureÄ‘ajima kako biste pristupili audio predavanju :)", "app.error.removed": "Uklonjeni ste sa predavanja", "app.error.meeting.ended": "Odjavili ste sa predavanja", - "app.meeting.logout.duplicateUserEjectReason": "Korisnik sa istim kredencijalima kao već postojeći korisnik pokuÅ¡ava da pristupi predavanju", + "app.meeting.logout.duplicateUserEjectReason": "UÄesnik sa istim pristupnim podacima kao već postojeći korisnik pokuÅ¡ava da pristupi predavanju", "app.meeting.logout.permissionEjectReason": "IzbaÄen zbog krÅ¡enja pravila", "app.meeting.logout.ejectedFromMeeting": "Uklonjeni ste sa predavanja", "app.meeting.logout.validateTokenFailedEjectReason": "Neuspela validacija autorizacionog tokena", - "app.meeting.logout.userInactivityEjectReason": "Preduga neaktivnost polaznika", + "app.meeting.logout.userInactivityEjectReason": "Preduga neaktivnost uÄesnika", "app.meeting-ended.rating.legendLabel": "Ocena povratnog utiska polaznika", "app.meeting-ended.rating.starLabel": "Zvezdica", "app.modal.close": "Zatvorite", @@ -451,25 +479,25 @@ "app.error.fallback.presentation.description": "Ulogovano. Molimo vas da pokuÅ¡ate sa ponovnim uÄitavanjem stranice.", "app.error.fallback.presentation.reloadButton": "UÄitajte ponovo", "app.guest.waiting": "ÄŒeka se odobrenje za pristup", - "app.userList.guest.waitingUsers": "ÄŒekaju se polaznici", - "app.userList.guest.waitingUsersTitle": "Upravljanje polaznicima", - "app.userList.guest.optionTitle": "Pregled polaznika na Äekanju", + "app.userList.guest.waitingUsers": "ÄŒekaju se uÄesnici", + "app.userList.guest.waitingUsersTitle": "Upravljanje uÄesnicima", + "app.userList.guest.optionTitle": "Pregled uÄesnika koji su na Äekanju", "app.userList.guest.allowAllAuthenticated": "Dozvolite pristup svima koji su proÅ¡li autentifikaciju", "app.userList.guest.allowAllGuests": "Dozvolite pristup svim gostima", "app.userList.guest.allowEveryone": "Dozvolite pristup svima", "app.userList.guest.denyEveryone": "Odbijte pristup svima", - "app.userList.guest.pendingUsers": "{0} polaznika na Äekanju", + "app.userList.guest.pendingUsers": "{0} uÄesnika na Äekanju", "app.userList.guest.pendingGuestUsers": "{0} gostiju na Äekanju", "app.userList.guest.pendingGuestAlert": "je pristupio sesiji i Äeka na vaÅ¡e odobrenje.", "app.userList.guest.rememberChoice": "Zapamtite izbor", "app.user-info.title": "Pretraga po direktorijumu", "app.toast.breakoutRoomEnded": "Odvojena soba je zatvorena. Molim vas da se ponovo pridružite audio kanalu.", - "app.toast.chat.public": "Nova javna poruka ", + "app.toast.chat.public": "Nova poruka u priÄaonici", "app.toast.chat.private": "Nova privatna poruka", "app.toast.chat.system": "Sistem", "app.toast.clearedEmoji.label": "Uklonjen status emotikona", "app.toast.setEmoji.label": "Status emotikona postavljen na {0}", - "app.toast.meetingMuteOn.label": "Svim polaznicima je ukinut glas", + "app.toast.meetingMuteOn.label": "Svim uÄesnicima je ukinut mikrofon", "app.toast.meetingMuteOff.label": "Ukidanje glasa za predavanje je iskljuÄeno", "app.notification.recordingStart": "Ova sesija se sada snima", "app.notification.recordingStop": "Ova sesija se ne snima", @@ -483,9 +511,9 @@ "app.shortcut-help.closeLabel": "Zatvorite", "app.shortcut-help.closeDesc": "Zatvorite prozor sa skraćenicama za tastaturu", "app.shortcut-help.openOptions": "Otvorite opcije", - "app.shortcut-help.toggleUserList": "UkljuÄite/iskljuÄite listu polaznika", + "app.shortcut-help.toggleUserList": "UkljuÄite/iskljuÄite listu uÄesnika", "app.shortcut-help.toggleMute": "Omogućite / ukinite glas", - "app.shortcut-help.togglePublicChat": "UkljuÄite/iskljuÄite javnu komunikaciju izmeÄ‘u polaznika (Lista polaznika mora biti otvorena)", + "app.shortcut-help.togglePublicChat": "UkljuÄite/iskljuÄite priÄaonicu (Lista uÄesnika mora biti otvorena)", "app.shortcut-help.hidePrivateChat": "Sakrijte privatni razgovor", "app.shortcut-help.closePrivateChat": "Zatvorite privatni razgovor", "app.shortcut-help.openActions": "Otvorite meni za akcije", @@ -493,22 +521,23 @@ "app.shortcut-help.togglePan": "Aktivirajte alatku za paniranje (PredavaÄ)", "app.shortcut-help.nextSlideDesc": "Sledeći slajd (PredavaÄ)", "app.shortcut-help.previousSlideDesc": "Prethodni slajd (PredavaÄ)", - "app.lock-viewers.title": "ZakljuÄajte gledaoce", - "app.lock-viewers.description": "Ove opcije vam omogućavaju da onemogućite gledaoce da koriste odreÄ‘ene funkcionalnosti.", + "app.lock-viewers.title": "ZakljuÄajte uÄesnike", + "app.lock-viewers.description": "Ove opcije vam omogućavaju da zabranite uÄesnicima korišćenje odreÄ‘enih funkcionalnosti.", "app.lock-viewers.featuresLable": "Karakteristika", "app.lock-viewers.lockStatusLabel": "Status", - "app.lock-viewers.webcamLabel": "Podelite prikaz veb kamere", - "app.lock-viewers.otherViewersWebcamLabel": "Pogledajte veb kamere ostalih gledalaca", + "app.lock-viewers.webcamLabel": "UkljuÄite svoju veb kameru", + "app.lock-viewers.otherViewersWebcamLabel": "Pogledajte veb kamere ostalih uÄesnika", "app.lock-viewers.microphoneLable": "Podelite zvuk sa mikrofona", - "app.lock-viewers.PublicChatLabel": "PoÅ¡aljite javnu poruku", + "app.lock-viewers.PublicChatLabel": "PoÅ¡aljite poruku u Äitaonici", "app.lock-viewers.PrivateChatLable": "PoÅ¡aljite privatnu poruku", - "app.lock-viewers.notesLabel": "Uredite beleÅ¡ke za sve uÄesnike", - "app.lock-viewers.userListLabel": "Pogledajte ostale gledaoce u listi polaznika", - "app.lock-viewers.ariaTitle": "ZakljuÄajte podeÅ¡avanja za gledaoce", + "app.lock-viewers.notesLabel": "Uredite beleÅ¡ke za uÄesnike", + "app.lock-viewers.userListLabel": "Pogledajte ostale uÄesnike u listi", + "app.lock-viewers.ariaTitle": "ZakljuÄajte podeÅ¡avanja za uÄesnike", "app.lock-viewers.button.apply": "Primenite", "app.lock-viewers.button.cancel": "Otkažite", "app.lock-viewers.locked": "ZakljuÄano", "app.lock-viewers.unlocked": "OtkljuÄano", + "app.connection-status.description": "Pogledajte status povezivanja uÄesnika", "app.recording.startTitle": "ZapoÄnite snimanje", "app.recording.stopTitle": "Pauzirajte snimanje", "app.recording.resumeTitle": "Nastavite snimanje", @@ -534,7 +563,7 @@ "app.video.notAllowed": "Nedostaje dozvola za prikaz sa veb kamere, uverite ste da ste je u pretraživaÄu dali ", "app.video.notSupportedError": "Prikaz sa veb kamere se može deliti samo iz sigurnih izvora, uverite se da je vaÅ¡ SSL sertifikat validan", "app.video.notReadableError": "Nismo mogli da dobijemo prikaz sa vaÅ¡e veb kamere. Proverite da li možda neki drugi program koristi veb kameru", - "app.video.suggestWebcamLock": "Primeniti zakljuÄavanje veb kamera gledalaca?", + "app.video.suggestWebcamLock": "Primeniti zakljuÄavanje veb kamera uÄesnika?", "app.video.suggestWebcamLockReason": "(ovo će unaprediti stabilnost predavanja)", "app.video.enable": "Omogućite", "app.video.cancel": "Otkažite", @@ -547,22 +576,10 @@ "app.video.videoMenuDesc": "Otvori meni za video", "app.video.chromeExtensionError": "Morate instalirati", "app.video.chromeExtensionErrorLink": "ovu Chrome ekstenziju", - "app.video.stats.title": "Statistika povezivanja", - "app.video.stats.packetsReceived": "Primljenih paketa", - "app.video.stats.packetsSent": "Poslatih paketa", - "app.video.stats.packetsLost": "Izgubljenih paketa", - "app.video.stats.bitrate": "Bitrate", - "app.video.stats.lostPercentage": "Procenat gubitka", - "app.video.stats.lostRecentPercentage": "Nedavni procenat gubitka", - "app.video.stats.dimensions": "Dimenzije", - "app.video.stats.codec": "Kodek", - "app.video.stats.decodeDelay": "KaÅ¡njenje dekodiranja", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Korišćenje enkodera", - "app.video.stats.currentDelay": "Trenutno kaÅ¡njenje", "app.fullscreenButton.label": "Postavite {0} na ceo ekran", "app.meeting.endNotification.ok.label": "OK", - "app.whiteboard.annotations.poll": "Rezultati ankete su objavljeni", + "app.whiteboard.annotations.poll": "Rezultati ankete su bili objavljeni", + "app.whiteboard.annotations.pollResult": "Rezultati ankete", "app.whiteboard.toolbar.tools": "Alatke", "app.whiteboard.toolbar.tools.hand": "Prelaz preko slika (pan)", "app.whiteboard.toolbar.tools.pencil": "Olovka", @@ -587,10 +604,10 @@ "app.whiteboard.toolbar.color.violet": "LjubiÄasta", "app.whiteboard.toolbar.color.magenta": "Roze", "app.whiteboard.toolbar.color.silver": "Srebrna", - "app.whiteboard.toolbar.undo": "Vrati na stanje pre ovog komentara", + "app.whiteboard.toolbar.undo": "Vrati na prethodno stanje", "app.whiteboard.toolbar.clear": "Uklonite sve komentare", - "app.whiteboard.toolbar.multiUserOn": "UkljuÄite tablu za crtanje za polaznike", - "app.whiteboard.toolbar.multiUserOff": "IskljuÄite tablu za crtanje za polaznike", + "app.whiteboard.toolbar.multiUserOn": "UkljuÄite tablu za crtanje za viÅ¡e uÄesnika", + "app.whiteboard.toolbar.multiUserOff": "IskljuÄite tablu za crtanje za viÅ¡e uÄesnika", "app.whiteboard.toolbar.fontSize": "Lista veliÄina fonta", "app.feedback.title": "Odjavili ste sa predavanja", "app.feedback.subtitle": "Voleli bismo da Äujemo vaÅ¡e utiske o BigBlueButton (opciono)", @@ -601,7 +618,7 @@ "app.videoDock.webcamFocusDesc": "Fokusirajte odabranu veb kameru", "app.videoDock.webcamUnfocusLabel": "Odfokusirajte", "app.videoDock.webcamUnfocusDesc": "Odfokusirajte odabranu kameru", - "app.videoDock.autoplayBlockedDesc": "Treba nam vaÅ¡a dozvola kako bismo vam pokazali prikaz sa veb kamera drugih polaznika.", + "app.videoDock.autoplayBlockedDesc": "Treba nam vaÅ¡a dozvola kako bismo vam pokazali prikaz sa veb kamera drugih uÄesnika.", "app.videoDock.autoplayAllowLabel": "Pogledajte veb kamere", "app.invitation.title": "Poziv za odvojenu sobu.", "app.invitation.confirm": "Pozovite", @@ -629,9 +646,9 @@ "app.createBreakoutRoom.minusRoomTime": "Skratite vreme odvojene sobe na", "app.createBreakoutRoom.addRoomTime": "Produžite vreme odvojene sobe na", "app.createBreakoutRoom.addParticipantLabel": "+ Dodajte uÄesnika", - "app.createBreakoutRoom.freeJoin": "Dozvolite polaznicima izbor odvojene sobe kojoj će se prikljuÄiti", - "app.createBreakoutRoom.leastOneWarnBreakout": "Morate postaviti barem jednog polaznika u odvojenu sobu", - "app.createBreakoutRoom.modalDesc": "Savet: Možete prevući ime polaznika kako biste ga dodelili odreÄ‘enoj odvojenoj sobi", + "app.createBreakoutRoom.freeJoin": "Dozvolite uÄesnicima izbor odvojene sobe kojoj će se prikljuÄiti", + "app.createBreakoutRoom.leastOneWarnBreakout": "Morate staviti barem jednog uÄesnika u odvojenu sobu", + "app.createBreakoutRoom.modalDesc": "Savet: Možete prevući ime uÄesnika kako biste ga stavili u odreÄ‘enu odvojenu sobu", "app.createBreakoutRoom.roomTime": "{0} minuta", "app.createBreakoutRoom.numberOfRoomsError": "Broj soba je nedozvoljen.", "app.externalVideo.start": "Podelite novi video fajl", @@ -643,7 +660,6 @@ "app.externalVideo.autoPlayWarning": "Reprodukujte video fajl kako biste omogućili sinhronizaciju", "app.network.connection.effective.slow": "Postoje problemi sa povezivanjem", "app.network.connection.effective.slow.help": "Dodatne informacije", - "app.externalVideo.noteLabel": "ObaveÅ¡tenje: Eksterni video fajlovi koje delite se neće nalaziti na snimku predavanja. YouTube, Vimeo, Instructure Media, Twitch i Daily Motion URL-ovi su podržani.", "app.actionsBar.actionsDropdown.shareExternalVideo": "Podelite eksterni video fajl", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Zaustavite deljenje eksternog video fajla", "app.iOSWarning.label": "Molimo vas da izvrÅ¡ite nadogradnju na verziju iOS-a 12.2 ili viÅ¡u", diff --git a/bigbluebutton-html5/private/locales/sv_SE.json b/bigbluebutton-html5/private/locales/sv_SE.json index 3aeb59bd7e6c509910f07bfd806eb94a5d550637..d9094a21d0c672e5c8bb9307d4c6ee82a1663556 100644 --- a/bigbluebutton-html5/private/locales/sv_SE.json +++ b/bigbluebutton-html5/private/locales/sv_SE.json @@ -47,10 +47,10 @@ "app.note.title": "Delad notiser", "app.note.label": "Notis", "app.note.hideNoteLabel": "Göm notis", + "app.note.tipLabel": "Tryck pÃ¥ Esc för att fokusera redigeringsverktygsfältet", "app.user.activityCheck": "Användaraktivitetskontroll", "app.user.activityCheck.label": "Kontrollera om användaren fortfarande är i möte ({0})", "app.user.activityCheck.check": "Kontrollera", - "app.note.tipLabel": "Tryck pÃ¥ Esc för att fokusera redigeringsverktygsfältet", "app.userList.usersTitle": "Användare", "app.userList.participantsTitle": "Deltagare", "app.userList.messagesTitle": "Meddelanden", @@ -118,8 +118,6 @@ "app.meeting.meetingTimeRemaining": "Ã…terstÃ¥ende mötestid: {0}", "app.meeting.meetingTimeHasEnded": "Tid slutade. Mötet stängs snart", "app.meeting.endedMessage": "Du kommer att vidarebefodras till startskärmen", - "app.meeting.alertMeetingEndsUnderOneMinute": "Mötet slutar om en minut", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Gruppmötet stängs om en minut.", "app.presentation.hide": "Göm presentationen", "app.presentation.notificationLabel": "Nuvarande presentation", "app.presentation.slideContent": "BildspelsinnehÃ¥ll", @@ -247,7 +245,6 @@ "app.leaveConfirmation.confirmLabel": "Lämna", "app.leaveConfirmation.confirmDesc": "Loggar dig ur mötet", "app.endMeeting.title": "Avsluta mötet", - "app.endMeeting.description": "Är du säker pÃ¥ att du vill avsluta den här sessionen?", "app.endMeeting.yesLabel": "Ja", "app.endMeeting.noLabel": "Nej", "app.about.title": "Om", @@ -268,8 +265,6 @@ "app.screenshare.screenShareLabel" : "Skärmdelning", "app.submenu.application.applicationSectionTitle": "Applikation", "app.submenu.application.animationsLabel": "Animeringar", - "app.submenu.application.audioAlertLabel": "Ljudvarning för chatt", - "app.submenu.application.pushAlertLabel": "Popup-varning för chatt", "app.submenu.application.fontSizeControlLabel": "Typsnittstorlek", "app.submenu.application.increaseFontBtnLabel": "Öka applikationstypsnittsstorlek", "app.submenu.application.decreaseFontBtnLabel": "Minska applikationstypsnittsstorlek", @@ -361,7 +356,7 @@ "app.audioModal.ariaTitle": "GÃ¥ med i ljudmodal", "app.audioModal.microphoneLabel": "Mikrofon", "app.audioModal.listenOnlyLabel": "Lyssna bara", - "app.audioModal.audioChoiceLabel": "Hur skull edu vilja gÃ¥ med ljud?", + "app.audioModal.audioChoiceLabel": "Hur skulle du vilja gÃ¥ med ljud?", "app.audioModal.iOSBrowser": "Audio/video stöds inte", "app.audioModal.iOSErrorDescription": "Vid denna tidpunkt stöds inte ljud och video pÃ¥ Chrome för iOS.", "app.audioModal.iOSErrorRecommendation": "Vi rekommenderar att du använder Safari iOS.", @@ -522,22 +517,8 @@ "app.video.videoMenuDesc": "Öppna videomenyns rullgardin", "app.video.chromeExtensionError": "Du mÃ¥ste installera", "app.video.chromeExtensionErrorLink": "det här Chrome-tillägget", - "app.video.stats.title": "Anslutningsstatistik", - "app.video.stats.packetsReceived": "Paket mottagna", - "app.video.stats.packetsSent": "Paket skickade", - "app.video.stats.packetsLost": "Packet förlorade", - "app.video.stats.bitrate": "Bitrate", - "app.video.stats.lostPercentage": "Total procentuell förlust", - "app.video.stats.lostRecentPercentage": "Senaste procentuella förlusten", - "app.video.stats.dimensions": "MÃ¥tt", - "app.video.stats.codec": "Kodek", - "app.video.stats.decodeDelay": "Avkodningsfördröjning", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Koda användningen", - "app.video.stats.currentDelay": "Nuvarande fördröjning", "app.fullscreenButton.label": "Gör {0} fullskärm", "app.meeting.endNotification.ok.label": "OK", - "app.whiteboard.annotations.poll": "Omröstningsresultaten publicerades", "app.whiteboard.toolbar.tools": "Verktyg", "app.whiteboard.toolbar.tools.hand": "Panorera", "app.whiteboard.toolbar.tools.pencil": "Penna", diff --git a/bigbluebutton-html5/private/locales/te.json b/bigbluebutton-html5/private/locales/te.json new file mode 100644 index 0000000000000000000000000000000000000000..bc0a30f8aaef823609edf1cfd6c9376ab7db1fb4 --- /dev/null +++ b/bigbluebutton-html5/private/locales/te.json @@ -0,0 +1,712 @@ +{ + "app.home.greeting": "మీ à°ªà±à°°à°¦à°°à±à°¶à°¨ à°¤à±à°µà°°à°²à±‹ à°ªà±à°°à°¾à°°à°‚à°à°®à°µà±à°¤à±à°‚ది ...", + "app.chat.submitLabel": "సందేశమౠపంపà±", + "app.chat.errorMaxMessageLength": "సందేశం {0} à°…à°•à±à°·à°°à°¾à°²à± (à°²à±) చాలా పొడవà±à°—à°¾ ఉంది", + "app.chat.disconnected": "మీరౠడిసà±à°•నెకà±à°Ÿà± చేయబడà±à°¡à°¾à°°à±, సందేశాలౠపంపబడవà±", + "app.chat.locked": "చాటౠలాకౠచేయబడింది, సందేశాలౠపంపబడవà±", + "app.chat.inputLabel": "చాటౠకోసం సందేశ ఇనà±à°ªà±à°Ÿà± {0}", + "app.chat.inputPlaceholder": "{0} కౠసందేశానà±à°¨à°¿ పంపండి", + "app.chat.titlePublic": "పబà±à°²à°¿à°•ౠచాటà±", + "app.chat.titlePrivate": "{0} తో à°ªà±à°°à±ˆà°µà±‡à°Ÿà± చాటà±", + "app.chat.partnerDisconnected": "{0} సమావేశం à°¨à±à°‚à°¡à°¿ నిషà±à°•à±à°°à°®à°¿à°‚చారà±", + "app.chat.closeChatLabel": "{0} మూసివేయి", + "app.chat.hideChatLabel": "{0} దాచà±", + "app.chat.moreMessages": "దిగà±à°µà±à°¨ మరినà±à°¨à°¿ సందేశాలà±", + "app.chat.dropdown.options": "చాటౠఎంపికలà±", + "app.chat.dropdown.clear": "à°•à±à°²à°¿à°¯à°°à± చాటà±", + "app.chat.dropdown.copy": "కాపీ చాటà±", + "app.chat.dropdown.save": "సేవౠచాటà±", + "app.chat.label": "చాటà±", + "app.chat.offline": "ఆఫà±à°²à±†à±–à°¨à±", + "app.chat.pollResult": "పోలౠఫలితాలà±", + "app.chat.emptyLogLabel": "చాటౠలాగౠఖాళీగా ఉంది", + "app.chat.clearPublicChatMessage": "పబà±à°²à°¿à°•ౠచాటౠచరితà±à°°à°¨à± మోడరేటరౠకà±à°²à°¿à°¯à°°à± చేశారà±", + "app.chat.multi.typing": "బహà±à°³ వినియోగదారà±à°²à± టైపౠచేసà±à°¤à±à°¨à±à°¨à°¾à°°à±", + "app.chat.one.typing": "{0} టైపౠచేసà±à°¤à±à°¨à±à°¨à°¾à°°à±", + "app.chat.two.typing": "{0} , {1} టైపౠచేసà±à°¤à±à°¨à±à°¨à°¾à°°à±", + "app.captions.label": "శీరà±à°·à°¿à°•à°²à±", + "app.captions.menu.close": "మూసివేయి", + "app.captions.menu.start": "à°ªà±à°°à°¾à°°à°‚à°à°¿à°‚à°šà±", + "app.captions.menu.ariaStart": "శీరà±à°·à°¿à°•లౠరాయడం à°ªà±à°°à°¾à°°à°‚à°à°¿à°‚à°šà±", + "app.captions.menu.ariaStartDesc": "శీరà±à°·à°¿à°•à°² à°Žà°¡à°¿à°Ÿà°°à±â€Œà°¨à± తెరిచి మోడలà±â€Œà°¨à± మూసివేసà±à°¤à±à°‚ది", + "app.captions.menu.select": "à°…à°‚à°¦à±à°¬à°¾à°Ÿà±à°²à±‹ ఉనà±à°¨ à°à°¾à°·à°¨à± à°Žà°‚à°šà±à°•ోండి", + "app.captions.menu.ariaSelect": "శీరà±à°·à°¿à°•à°² à°à°¾à°·", + "app.captions.menu.subtitle": "దయచేసి మీ సెషనà±â€Œà°²à±‹ శీరà±à°·à°¿à°•à°² కోసం à°à°¾à°· మరియౠశైలిని à°Žà°‚à°šà±à°•ోండి.", + "app.captions.menu.title": "శీరà±à°·à°¿à°•à°²à±", + "app.captions.menu.fontSize": "పరిమాణం", + "app.captions.menu.fontColor": "వచన à°°à°‚à°—à±", + "app.captions.menu.fontFamily": "ఫాంటà±", + "app.captions.menu.backgroundColor": "à°¬à±à°¯à°¾à°•à± à°—à±à°°à±Œà°‚à°¡à± à°°à°‚à°—à±", + "app.captions.menu.previewLabel": "à°ªà±à°°à°¿à°µà±à°¯à±‚", + "app.captions.menu.cancelLabel": "à°°à°¦à±à°¦à± చేయి", + "app.captions.pad.hide": "శీరà±à°·à°¿à°•లౠదాచà±", + "app.captions.pad.tip": "ఎడిటరౠటూలà±â€Œà°¬à°¾à°°à±â€Œ పై దృషà±à°Ÿà°¿ పెటà±à°Ÿà°¡à°¾à°¨à°¿à°•à°¿ Esc నొకà±à°•à°‚à°¡à°¿", + "app.captions.pad.ownership": "à°¸à±à°µà°¾à°§à±€à°¨à°‚ చేసà±à°•ోండి", + "app.captions.pad.ownershipTooltip": "మీరౠ{0} శీరà±à°·à°¿à°•à°² యజమానిగా కేటాయించబడతారà±", + "app.captions.pad.interimResult": "మధà±à°¯à°‚తర ఫలితాలà±", + "app.captions.pad.dictationStart": "à°¡à°¿à°•à±à°Ÿà±‡à°·à°¨à± à°ªà±à°°à°¾à°°à°‚à°à°¿à°‚à°šà°‚à°¡à°¿", + "app.captions.pad.dictationStop": "à°¡à°¿à°•à±à°Ÿà±‡à°·à°¨à± ఆపà±à°®à±", + "app.captions.pad.dictationOnDesc": "మాటల à°—à±à°°à±à°¤à°¿à°‚à°ªà±à°¨à± ఆనౠచేయండి", + "app.captions.pad.dictationOffDesc": " మాటల à°—à±à°°à±à°¤à°¿à°‚à°ªà±à°¨à± ఆపివేసà±à°¤à±à°‚ది", + "app.note.title": "షేరà±à°¡à± నోటà±à°¸à±", + "app.note.label": "నోటà±", + "app.note.hideNoteLabel": "నోటà±à°¨à± దాచండి", + "app.note.tipLabel": "ఎడిటరౠటూలà±â€Œà°¬à°¾à°°à±â€Œ పై దృషà±à°Ÿà°¿ పెటà±à°Ÿà°¡à°¾à°¨à°¿à°•à°¿ Esc నొకà±à°•à°‚à°¡à°¿", + "app.user.activityCheck": "వినియోగదారà±à°¨à°¿ పనిని పరిశీలించండి", + "app.user.activityCheck.label": "వినియోగదారà±à°¡à± ఇంకా సమావేశంలో ఉనà±à°¨à°¾à°°à±‹ లేదో పరిశీలించండి ({0})", + "app.user.activityCheck.check": "పరిశీలించండి", + "app.userList.usersTitle": "వినియోగదారà±à°²à±", + "app.userList.participantsTitle": "పాలà±à°—ొనేవారà±", + "app.userList.messagesTitle": "సందేశాలà±", + "app.userList.notesTitle": "నోటà±à°¸à±", + "app.userList.notesListItem.unreadContent": "షేరà±à°¡à± నోటà±à°¸à± విà°à°¾à°—ంలో à°•à±à°°à±Šà°¤à±à°¤ కంటెంటౠఅందà±à°¬à°¾à°Ÿà±à°²à±‹ ఉంది", + "app.userList.captionsTitle": "శీరà±à°·à°¿à°•à°²à±", + "app.userList.presenter": "à°ªà±à°°à±†à°œà±†à°‚à°Ÿà°°à±", + "app.userList.you": "మీరà±", + "app.userList.locked": "లాకౠచేయబడింది", + "app.userList.byModerator": "(మోడరేటరà±) à°¦à±à°µà°¾à°°à°¾", + "app.userList.label": "వినియోగదారà±à°¨à°¿ జాబితా", + "app.userList.toggleCompactView.label": "కాంపాకà±à°Ÿà± à°µà±à°¯à±‚ మోడà±â€Œà°¨à± టోగà±à°²à± చేయండి", + "app.userList.guest": "అతిథి", + "app.userList.menuTitleContext": "à°…à°‚à°¦à±à°¬à°¾à°Ÿà±à°²à±‹ ఉనà±à°¨ ఎంపికలà±", + "app.userList.chatListItem.unreadSingular": "{0} కొతà±à°¤ సందేశం", + "app.userList.chatListItem.unreadPlural": "{0} కొతà±à°¤ సందేశాలà±", + "app.userList.menu.chat.label": "à°ªà±à°°à±ˆà°µà±‡à°Ÿà± చాటౠపà±à°°à°¾à°°à°‚à°à°¿à°‚à°šà°‚à°¡à°¿", + "app.userList.menu.clearStatus.label": "à°¸à±à°Ÿà±‡à°Ÿà°¸à± ని à°•à±à°²à°¿à°¯à°°à± చేయండి", + "app.userList.menu.removeUser.label": "వినియోగదారà±à°¨à°¿ తొలగించండి", + "app.userList.menu.removeConfirmation.label": "వినియోగదారà±à°¨à°¿ తొలగించండి ({0})", + "app.userlist.menu.removeConfirmation.desc": "à°ˆ వినియోగదారà±à°¨à°¿ సెషనà±â€Œà°²à±‹ తిరిగి చేరకà±à°‚à°¡à°¾ ఆపేయండి", + "app.userList.menu.muteUserAudio.label": "à°®à±à°¯à±‚టౠచేయి", + "app.userList.menu.unmuteUserAudio.label": "à°…à°¨à±â€Œà°®à±à°¯à±‚టౠచేయి", + "app.userList.userAriaLabel": "{0} {1} {2} à°¸à±à°¥à°¿à°¤à°¿ {3}", + "app.userList.menu.promoteUser.label": "మోడరేటరà±â€Œà°—à°¾ చేయి", + "app.userList.menu.demoteUser.label": "వీకà±à°·à°•à±à°¡à°¿à°—à°¾ మారà±à°šà±à°®à±", + "app.userList.menu.unlockUser.label": "అనౠలాకౠ{0}", + "app.userList.menu.lockUser.label": "లాకà±{0}", + "app.userList.menu.directoryLookup.label": "డైరెకà±à°Ÿà°°à±€ చూడండి", + "app.userList.menu.makePresenter.label": "à°ªà±à°°à±†à°œà±†à°‚టరౠచేయండి", + "app.userList.userOptions.manageUsersLabel": "వినియోగదారà±à°²à°¨à± à°®à±à°¯à°¾à°¨à±‡à°œà± చేయండి", + "app.userList.userOptions.muteAllLabel": "అందరినీ à°®à±à°¯à±‚టౠచేయి", + "app.userList.userOptions.muteAllDesc": "సమావేశంలో అందరినీ à°®à±à°¯à±‚టౠచేయి", + "app.userList.userOptions.clearAllLabel": "à°…à°¨à±à°¨à°¿ à°¸à±à°¥à°¿à°¤à°¿ à°šà°¿à°¹à±à°¨à°¾à°²à°¨à± à°•à±à°²à°¿à°¯à°°à± చేయండి", + "app.userList.userOptions.clearAllDesc": "అందరి à°¸à±à°¥à°¿à°¤à°¿ à°šà°¿à°¹à±à°¨à°¾à°²à°¨à± à°•à±à°²à°¿à°¯à°°à± చేయి", + "app.userList.userOptions.muteAllExceptPresenterLabel": "à°ªà±à°°à±†à°œà±†à°‚టరౠమినహా అందరినీ à°®à±à°¯à±‚టౠచేయి", + "app.userList.userOptions.muteAllExceptPresenterDesc": "à°ªà±à°°à±†à°œà±†à°‚టరౠమినహా సమావేశంలోని అందరినీ à°®à±à°¯à±‚టౠచేయి", + "app.userList.userOptions.unmuteAllLabel": "సమావేశానà±à°¨à°¿ à°®à±à°¯à±‚టౠఆఫౠచేయండి", + "app.userList.userOptions.unmuteAllDesc": "సమావేశానà±à°¨à°¿ à°…à°¨à±â€Œà°®à±à°¯à±‚టౠచేసà±à°¤à±à°‚ది", + "app.userList.userOptions.lockViewersLabel": "వీకà±à°·à°•à±à°²à°¨à± లాకౠచేయి", + "app.userList.userOptions.lockViewersDesc": "సమావేశానికి హాజరయà±à°¯à±‡à°µà°¾à°°à°¿à°•à°¿ కొనà±à°¨à°¿ కారà±à°¯à°¾à°šà°°à°£à°²à°¨à± లాకౠచేయండి", + "app.userList.userOptions.connectionStatusLabel": "కనెకà±à°·à°¨à± à°¸à±à°¥à°¿à°¤à°¿", + "app.userList.userOptions.connectionStatusDesc": "అందరి కనెకà±à°·à°¨à± à°¸à±à°¥à°¿à°¤à°¿à°¨à°¿ చూడండి", + "app.userList.userOptions.disableCam": "వీకà±à°·à°•à±à°² వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œà°²à± ఆఫౠచేయబడà±à°¡à°¾à°¯à°¿", + "app.userList.userOptions.disableMic": "వీకà±à°·à°•à±à°² మైకà±à°°à±‹à°«à±‹à°¨à±à°²à± ఆఫౠచేయబడà±à°¡à°¾à°¯à°¿", + "app.userList.userOptions.disablePrivChat": "à°ªà±à°°à±ˆà°µà±‡à°Ÿà± చాటౠఆఫౠచేయబడింది", + "app.userList.userOptions.disablePubChat": "పబà±à°²à°¿à°•ౠచాటౠఆఫౠచేయబడింది", + "app.userList.userOptions.disableNote": "షేరà±à°¡à± నోటà±à°¸à±à°²à± ఇపà±à°ªà±à°¡à± లాకౠచేయబడà±à°¡à°¾à°¯à°¿", + "app.userList.userOptions.hideUserList": "వినియోగదారౠని జాబితా ఇపà±à°ªà±à°¡à± వీకà±à°·à°•à±à°²à°•ౠదాగి ఉంది", + "app.userList.userOptions.webcamsOnlyForModerator": "మోడరేటరà±à°²à± మాతà±à°°à°®à±‡ వీకà±à°·à°•à±à°² వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œà°²à°¨à± చూడగలరౠ(లాకౠసెటà±à°Ÿà°¿à°‚à°—à±â€Œà°² కారణంగా)", + "app.userList.content.participants.options.clearedStatus": "అందరి à°¸à±à°¥à°¿à°¤à°¿ à°•à±à°²à°¿à°¯à°°à± చేయబడింది", + "app.userList.userOptions.enableCam": "వీకà±à°·à°•à±à°² వెబà±â€Œà°•à±à°¯à°¾à°®à± ‌లౠఆనౠచేయబడà±à°¡à°¾à°¯à°¿", + "app.userList.userOptions.enableMic": "వీకà±à°·à°•à±à°² మైకà±à°°à±‹à°«à±‹à°¨à±à°²à± ఆనౠచేయబడà±à°¡à°¾à°¯à°¿", + "app.userList.userOptions.enablePrivChat": "à°ªà±à°°à±ˆà°µà±‡à°Ÿà± చాటౠఆనౠచేయబడింది", + "app.userList.userOptions.enablePubChat": "పబà±à°²à°¿à°•ౠచాటౠఆనౠచేయబడింది", + "app.userList.userOptions.enableNote": "షేరà±à°¡à± నోటà±à°¸à±à°²à± ఇపà±à°ªà±à°¡à± à°ªà±à°°à°¾à°°à°‚à°à°¿à°‚చబడà±à°¡à°¾à°¯à°¿", + "app.userList.userOptions.showUserList": "వినియోగదారà±à°¨à°¿ జాబితా ఇపà±à°ªà±à°¡à± వీకà±à°·à°•à±à°²à°•ౠచూపబడింది", + "app.userList.userOptions.enableOnlyModeratorWebcam": "మీరౠఇపà±à°ªà±à°¡à± మీ వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œà°¨à± ఆనౠచేయవచà±à°šà±,అందరూ మిమà±à°®à°²à±à°¨à°¿ చూసà±à°¤à°¾à°°à±", + "app.media.label": "మీడియా", + "app.media.autoplayAlertDesc": "యాకà±à°¸à±†à°¸à± ఇవà±à°µà°‚à°¡à°¿", + "app.media.screenshare.start": "à°¸à±à°•à±à°°à±€à°¨à± షేరౠపà±à°°à°¾à°°à°‚à°à°®à±ˆà°‚ది", + "app.media.screenshare.end": "à°¸à±à°•à±à°°à±€à°¨à± షేరౠమà±à°—ిసింది", + "app.media.screenshare.unavailable": "à°¸à±à°•à±à°°à±€à°¨à± షేరౠఅందà±à°¬à°¾à°Ÿà±à°²à±‹ లేదà±", + "app.media.screenshare.notSupported": "à°ˆ à°¬à±à°°à±Œà°œà°°à±â€Œà°²à±‹ à°¸à±à°•à±à°°à±€à°¨à±â€Œà°·à±‡à°°à°¿à°‚à°—à±â€Œà°•ౠమదà±à°¦à°¤à± లేదà±.", + "app.media.screenshare.autoplayBlockedDesc": "à°ªà±à°°à±†à°œà±†à°‚à°Ÿà°°à± à°¸à±à°•à±à°°à±€à°¨à±â€Œà°¨à± మీకౠచూపించడానికి మాకౠమీ à°…à°¨à±à°®à°¤à°¿ అవసరం.", + "app.media.screenshare.autoplayAllowLabel": "షేరà±à°¦à± à°¸à±à°•à±à°°à±€à°¨à± ‌నౠచూడండి", + "app.screenshare.notAllowed": "లోపం: à°¸à±à°•à±à°°à±€à°¨à±â€Œà°¨à± యాకà±à°¸à±†à°¸à± చేయడానికి à°…à°¨à±à°®à°¤à°¿ ఇవà±à°µà°¬à°¡à°²à±‡à°¦à±.", + "app.screenshare.notSupportedError": "లోపం: à°¸à±à°•à±à°°à±€à°¨à± షేరింగౠసà±à°°à°•à±à°·à°¿à°¤à°®à±ˆà°¨ (SSL) డొమైనà±à°²à°²à±‹ మాతà±à°°à°®à±‡ à°…à°¨à±à°®à°¤à°¿à°‚చబడà±à°¤à±à°‚ది", + "app.screenshare.notReadableError": "లోపం: à°ªà±à°°à°¯à°¤à±à°¨à°¿à°¸à±à°¤à±à°¨à±à°¨à°ªà±à°ªà±à°¡à± వైఫలà±à°¯à°‚ ఉంది", + "app.screenshare.genericError": "లోపం: à°¸à±à°•à±à°°à±€à°¨à± షేరింగà±â€Œà°²à±‹ లోపం సంà°à°µà°¿à°‚చింది, దయచేసి మళà±à°²à±€ à°ªà±à°°à°¯à°¤à±à°¨à°¿à°‚à°šà°‚à°¡à°¿", + "app.meeting.ended": "à°ˆ సెషనౠమà±à°—ిసింది", + "app.meeting.meetingTimeRemaining": "సమావేశ సమయం మిగిలి ఉంది: {0}", + "app.meeting.meetingTimeHasEnded": "సమయం à°®à±à°—ిసింది. సమావేశం à°¤à±à°µà°°à°²à±‹ à°®à±à°—à±à°¸à±à°¤à±à°‚ది", + "app.meeting.endedMessage": "మీరౠహోమౠసà±à°•à±à°°à±€à°¨à±â€Œà°•ౠతిరిగి ఫారౠవరà±à°¡à± చేయబడతారà±", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "సమావేశం à°’à°• నిమిషంలో à°®à±à°—à±à°¸à±à°¤à±à°‚ది.", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "సమావేశం {0} నిమిషాలà±à°²à±‹ à°®à±à°—à±à°¸à±à°¤à±à°‚ది.", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "à°¬à±à°°à±‡à°•à± à°…à°µà±à°Ÿà± {0} నిమిషాలà±à°²à±‹ మూసివేయబడà±à°¤à±à°‚ది.", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "à°¬à±à°°à±‡à°•à± à°…à°µà±à°Ÿà± à°’à°• నిమిషంలో à°®à±à°—à±à°¸à±à°¤à±à°‚ది.", + "app.presentation.hide": "à°ªà±à°°à°¦à°°à±à°¶à°¨à°¨à± దాచండి", + "app.presentation.notificationLabel": "à°ªà±à°°à°¸à±à°¤à±à°¤ à°ªà±à°°à°¦à°°à±à°¶à°¨", + "app.presentation.downloadLabel": "డౌనà±à°²à±‹à°¡à±", + "app.presentation.slideContent": "à°¸à±à°²à°¯à°¿à°¡à± కంటెంటà±", + "app.presentation.startSlideContent": "à°¸à±à°²à°¯à°¿à°¡à± కంటెంటౠపà±à°°à°¾à°°à°‚à°à°‚ à°…à°¯à±à°¯à°¿à°‚ది", + "app.presentation.endSlideContent": "à°¸à±à°²à°¯à°¿à°¡à± కంటెంటౠమà±à°—ిసింది", + "app.presentation.emptySlideContent": "à°ªà±à°°à°¸à±à°¤à±à°¤ à°¸à±à°²à°¯à°¿à°¡à± కోసం కంటెంటౠలేదà±", + "app.presentation.presentationToolbar.noNextSlideDesc": "à°ªà±à°°à°¦à°°à±à°¶à°¨ à°®à±à°—à°¿à°‚à°ªà±", + "app.presentation.presentationToolbar.noPrevSlideDesc": "à°ªà±à°°à°¦à°°à±à°¶à°¨ à°ªà±à°°à°¾à°°à°‚à°à°‚", + "app.presentation.presentationToolbar.selectLabel": "à°¸à±à°²à°¯à°¿à°¡à± à°Žà°‚à°šà±à°•ోండి", + "app.presentation.presentationToolbar.prevSlideLabel": "à°®à±à°¨à±à°ªà°Ÿà°¿ à°¸à±à°²à°¯à°¿à°¡à±", + "app.presentation.presentationToolbar.prevSlideDesc": "à°ªà±à°°à°¦à°°à±à°¶à°¨à°¨à± à°®à±à°¨à±à°ªà°Ÿà°¿ à°¸à±à°²à°¯à°¿à°¡à± à°•à°¿ మారà±à°šà°‚à°¡à°¿", + "app.presentation.presentationToolbar.nextSlideLabel": "తరà±à°µà°¾à°¤à°¿ à°¸à±à°²à°¯à°¿à°¡à±", + "app.presentation.presentationToolbar.nextSlideDesc": "à°ªà±à°°à°¦à°°à±à°¶à°¨à°¨à± తదà±à°ªà°°à°¿ à°¸à±à°²à°¯à°¿à°¡à±â€Œà°•ౠమారà±à°šà°‚à°¡à°¿", + "app.presentation.presentationToolbar.skipSlideLabel": "à°¸à±à°²à°¯à°¿à°¡à± దాటివేయండి", + "app.presentation.presentationToolbar.skipSlideDesc": "à°ªà±à°°à°¦à°°à±à°¶à°¨à°¨à± నిరà±à°¦à°¿à°·à±à°Ÿ à°¸à±à°²à°¯à°¿à°¡à± ‌కి మారà±à°šà°‚à°¡à°¿", + "app.presentation.presentationToolbar.fitWidthLabel": "వెడలà±à°ªà±à°•ౠసరిపెటà±à°Ÿà±", + "app.presentation.presentationToolbar.fitWidthDesc": "à°¸à±à°²à°¯à°¿à°¡à± యొకà±à°• మొతà±à°¤à°‚ వెడలà±à°ªà±à°¨à± à°ªà±à°°à°¦à°°à±à°¶à°¿à°‚à°šà°‚à°¡à°¿", + "app.presentation.presentationToolbar.fitScreenLabel": "à°¸à±à°•à±à°°à±€à°¨à±â€Œà°•ౠసరిపెటà±à°Ÿà±", + "app.presentation.presentationToolbar.fitScreenDesc": "మొతà±à°¤à°‚ à°¸à±à°²à°¯à°¿à°¡à± నౠపà±à°°à°¦à°°à±à°¶à°¿à°‚à°šà±", + "app.presentation.presentationToolbar.zoomLabel": "జూమౠచెయà±à°¯à°‚à°¡à°¿", + "app.presentation.presentationToolbar.zoomDesc": "à°ªà±à°°à°¦à°°à±à°¶à°¨ యొకà±à°• జూమౠసà±à°¥à°¾à°¯à°¿à°¨à°¿ మారà±à°šà°‚à°¡à°¿", + "app.presentation.presentationToolbar.zoomInLabel": "జూమౠఇనà±", + "app.presentation.presentationToolbar.zoomInDesc": "à°ªà±à°°à°¦à°°à±à°¶à°¨à°²à±‹ జూమౠచేయండి", + "app.presentation.presentationToolbar.zoomOutLabel": "జూమౠఅవà±à°Ÿà±", + "app.presentation.presentationToolbar.zoomOutDesc": "à°ªà±à°°à°¦à°°à±à°¶à°¨ à°¨à±à°‚à°¡à°¿ జూమౠఅవà±à°Ÿà± చేయండి", + "app.presentation.presentationToolbar.zoomReset": "జూమà±â€Œà°¨à± రీసెటౠచేయండి", + "app.presentation.presentationToolbar.zoomIndicator": "à°ªà±à°°à°¸à±à°¤à±à°¤ జూమౠశాతం", + "app.presentation.presentationToolbar.fitToWidth": "వెడలà±à°ªà±à°•ౠసరిపెటà±à°Ÿà±", + "app.presentation.presentationToolbar.fitToPage": "పేజీకి సరిపెటà±à°Ÿà±", + "app.presentation.presentationToolbar.goToSlide": "à°¸à±à°²à°¯à°¿à°¡à± {0}", + "app.presentationUploder.title": "à°ªà±à°°à°¦à°°à±à°¶à°¨", + "app.presentationUploder.message": "à°ªà±à°°à±†à°œà±†à°‚à°Ÿà°°à±à°—à°¾ మీకౠà°à°¦à±ˆà°¨à°¾ కారà±à°¯à°¾à°²à°¯ పతà±à°°à°‚ లేదా PDF ఫైలà±â€Œà°¨à± à°…à°ªà±â€Œà°²à±‹à°¡à± చేసే సామరà±à°¥à±à°¯à°‚ ఉంది. ఉతà±à°¤à°® ఫలితాల కోసం మేమౠPDF ఫైలà±â€Œà°¨à± సిఫారà±à°¸à± చేసà±à°¤à±à°¨à±à°¨à°¾à°®à±. దయచేసి à°•à±à°¡à°¿ వైపà±à°¨ ఉనà±à°¨ సరà±à°•ిలౠచెకà±â€Œà°¬à°¾à°•à±à°¸à± ఉపయోగించి à°ªà±à°°à°¦à°°à±à°¶à°¨à°¨à± à°Žà°‚à°šà±à°•à±à°¨à±à°¨à°¾à°°à°¨à°¿ నిరà±à°§à°¾à°°à°¿à°‚à°šà±à°•ోండి.", + "app.presentationUploder.uploadLabel": "à°…à°ªà±à°²à±‹à°¡à± ", + "app.presentationUploder.confirmLabel": "à°•à°¨à±à°«à°°à±à°®à±", + "app.presentationUploder.confirmDesc": "మీ మారà±à°ªà±à°²à°¨à± సేవౠచేసి à°ªà±à°°à°¦à°°à±à°¶à°¨à°¨à± à°ªà±à°°à°¾à°°à°‚à°à°¿à°‚à°šà°‚à°¡à°¿", + "app.presentationUploder.dismissLabel": "à°°à°¦à±à°¦à± చేయి ", + "app.presentationUploder.dismissDesc": "మోడలౠవిండోనౠమూసివేసి, మీ మారà±à°ªà±à°²à°¨à± వదిలివెయà±à°¯à°‚à°¡à°¿", + "app.presentationUploder.dropzoneLabel": "ఫైలà±â€Œà°²à°¨à± à°…à°ªà±â€Œà°²à±‹à°¡à± చేయడానికి ఇకà±à°•à°¡ à°¡à±à°°à°¾à°—ౠచేయండి", + "app.presentationUploder.dropzoneImagesLabel": "à°šà°¿à°¤à±à°°à°¾à°²à°¨à± à°…à°ªà±â€Œà°²à±‹à°¡à± చేయడానికి ఇకà±à°•à°¡ à°¡à±à°°à°¾à°—ౠచేయండి", + "app.presentationUploder.browseFilesLabel": "లేదా ఫైళà±à°³ కోసం వెతకండి", + "app.presentationUploder.browseImagesLabel": "లేదా à°šà°¿à°¤à±à°°à°¾à°² కోసం à°¬à±à°°à±Œà°œà± / à°•à±à°¯à°¾à°ªà±à°šà°°à± చేయండి", + "app.presentationUploder.fileToUpload": "à°…à°ªà±â€Œà°²à±‹à°¡à± చేయబడాలి ...", + "app.presentationUploder.currentBadge": "à°ªà±à°°à°¸à±à°¤à±à°¤", + "app.presentationUploder.rejectedError": "à°Žà°‚à°šà±à°•à±à°¨à±à°¨ ఫైలౠ(à°²à±) తిరసà±à°•రించబడà±à°¡à°¾à°¯à°¿. దయచేసి ఫైలౠరకం (à°²) నౠపరిశీలించండి.", + "app.presentationUploder.upload.progress": "à°…à°ªà±â€Œà°²à±‹à°¡à± à°…à°µà±à°¤à±‹à°‚ది ({0}%)", + "app.presentationUploder.upload.413": "ఫైలౠచాలా పెదà±à°¦à°¦à°¿. దయచేసి బహà±à°³ ఫైలà±â€Œà°²à±à°—à°¾ విà°à°œà°¿à°‚à°šà°‚à°¡à°¿.", + "app.presentationUploder.genericError": "à°…à°¯à±à°¯à±‹, à°à°¦à±‹ తపà±à°ªà± జరిగింది...", + "app.presentationUploder.upload.408": "కొరà±à°•à±à°¨à±à°¨ à°…à°ªà±â€Œà°²à±‹à°¡à± టోకెనౠసమయం à°®à±à°—ిసింది.", + "app.presentationUploder.upload.404": "404: à°…à°ªà±â€Œà°²à±‹à°¡à± టోకెనౠచెలà±à°²à°¨à°¿à°¦à°¿", + "app.presentationUploder.upload.401": "కొరà±à°•à±à°¨à±à°¨ à°ªà±à°°à°¦à°°à±à°¶à°¨ à°…à°ªà±â€Œà°²à±‹à°¡à± టోకెనౠవిఫలమైంది.", + "app.presentationUploder.conversion.conversionProcessingSlides": " {1} యొకà±à°• {0} పేజీని à°ªà±à°°à°¾à°¸à±†à°¸à± చేసà±à°¤à±‹à°‚ది", + "app.presentationUploder.conversion.genericConversionStatus": "ఫైలà±à°¨à± మారà±à°¸à±à°¤à±‹à°‚ది ...", + "app.presentationUploder.conversion.generatingThumbnail": "తంబౠనైలà±à°¸à± రూపొందà±à°¤à±à°¨à±à°¨à°¾à°¯à°¿ ...", + "app.presentationUploder.conversion.generatedSlides": "à°¸à±à°²à°¯à°¿à°¡à± ‌లౠరూపొందాయి ...", + "app.presentationUploder.conversion.generatingSvg": "SVG à°šà°¿à°¤à±à°°à°¾à°²à± రూపొందà±à°¤à±à°¨à±à°¨à°¾à°¯à°¿...", + "app.presentationUploder.conversion.pageCountExceeded": "పేజీల సంఖà±à°¯ మించిపోయింది. దయచేసి ఫైలà±â€Œà°¨à± బహà±à°³ ఫైలà±â€Œà°²à±à°—à°¾ విà°à°œà°¿à°‚à°šà°‚à°¡à°¿.", + "app.presentationUploder.conversion.officeDocConversionInvalid": "ఆఫీసౠడాకà±à°¯à±à°®à±†à°‚à°Ÿà± à°ªà±à°°à°¾à°¸à±†à°¸à± చేయడంలో విఫలమైంది. బదà±à°²à±à°—à°¾ à°’à°• PDF ని à°…à°ªà±â€Œà°²à±‹à°¡à± చేయండి.", + "app.presentationUploder.conversion.officeDocConversionFailed": "ఆఫీసౠడాకà±à°¯à±à°®à±†à°‚à°Ÿà± à°ªà±à°°à°¾à°¸à±†à°¸à± చేయడంలో విఫలమైంది. బదà±à°²à±à°—à°¾ à°’à°• PDF ని à°…à°ªà±â€Œà°²à±‹à°¡à± చేయండి.", + "app.presentationUploder.conversion.pdfHasBigPage": "మేమౠPDF ఫైలà±â€Œà°¨à± మారà±à°šà°²à±‡à°•పోయామà±, దయచేసి దానà±à°¨à°¿ à°…à°¨à±à°—à±à°£à°‚à°—à°¾ చేయడానికి à°ªà±à°°à°¯à°¤à±à°¨à°¿à°‚à°šà°‚à°¡à°¿", + "app.presentationUploder.conversion.timeout": "à°…à°¯à±à°¯à±‹, మారà±à°ªà°¿à°¡à°¿ చాలా సమయం పటà±à°Ÿà°¿à°‚ది", + "app.presentationUploder.conversion.pageCountFailed": "పేజీల సంఖà±à°¯à°¨à± నిరà±à°£à°¯à°¿à°‚చడంలో విఫలమైంది.", + "app.presentationUploder.isDownloadableLabel": "à°ªà±à°°à°¦à°°à±à°¶à°¨à°¨à± డౌనà±â€Œà°²à±‹à°¡à± చేయడానికి à°…à°¨à±à°®à°¤à°¿à°‚చవదà±à°¦à±", + "app.presentationUploder.isNotDownloadableLabel": "à°ªà±à°°à°¦à°°à±à°¶à°¨à°¨à± డౌనà±â€Œà°²à±‹à°¡à± చేయడానికి à°…à°¨à±à°®à°¤à°¿à°‚à°šà°‚à°¡à°¿", + "app.presentationUploder.removePresentationLabel": "à°ªà±à°°à°¦à°°à±à°¶à°¨à°¨à± తొలగించండి", + "app.presentationUploder.setAsCurrentPresentation": "à°ªà±à°°à°¦à°°à±à°¶à°¨à°¨à± à°ªà±à°°à°¸à±à°¤à±à°¤à°‚à°—à°¾ సెటౠచేయండి", + "app.presentationUploder.tableHeading.filename": "ఫైలౠపేరà±", + "app.presentationUploder.tableHeading.options": "ఎంపికలà±", + "app.presentationUploder.tableHeading.status": "à°¸à±à°¥à°¿à°¤à°¿", + "app.presentationUploder.uploading": "à°…à°ªà±â€Œà°²à±‹à°¡à± à°…à°µà±à°¤à±‹à°‚ది {0} {1}", + "app.presentationUploder.uploadStatus": " {1} యొకà±à°• {0} à°…à°ªà±â€Œà°²à±‹à°¡à±â€Œà°²à± పూరà±à°¤à°¯à±à°¯à°¾à°¯à°¿", + "app.presentationUploder.completed": " {0} à°…à°ªà±â€Œà°²à±‹à°¡à±â€Œà°²à± పూరà±à°¤à°¯à±à°¯à°¾à°¯à°¿", + "app.presentationUploder.item" : "à°…à°‚à°¶à°‚", + "app.presentationUploder.itemPlural" : "అంశాలà±", + "app.presentationUploder.clearErrors": "లోపాలనౠకà±à°²à°¿à°¯à°°à± చేయండి", + "app.presentationUploder.clearErrorsDesc": "విఫలమైన à°ªà±à°°à°¦à°°à±à°¶à°¨ à°…à°ªà±â€Œà°²à±‹à°¡à±â€Œà°²à°¨à± à°•à±à°²à°¿à°¯à°°à± చేసà±à°¤à±à°‚ది", + "app.poll.pollPaneTitle": "పోలింగà±", + "app.poll.quickPollTitle": "తకà±à°·à°£ à°Žà°¨à±à°¨à°¿à°•", + "app.poll.hidePollDesc": "పోలౠమెనౠపేనà±â€Œà°¨à± దాచిపెడà±à°¤à±à°‚ది", + "app.poll.customPollInstruction": "à°…à°¨à±à°•ూల పోలà±â€Œà°¨à± సృషà±à°Ÿà°¿à°‚చడానికి, దిగà±à°µ బటనà±â€Œà°¨à± à°Žà°‚à°šà±à°•ోండి మరియౠమీ ఎంపికలనౠఇనà±â€Œà°ªà±à°Ÿà± చేయండి.", + "app.poll.quickPollInstruction": "మీ పోలౠపà±à°°à°¾à°°à°‚à°à°¿à°‚చడానికి à°•à±à°°à°¿à°‚ది ఎంపికనౠఎంచà±à°•ోండి.", + "app.poll.customPollLabel": "à°•à°¸à±à°Ÿà°®à± పోలà±", + "app.poll.startCustomLabel": "పోలà±â€Œà°¨à± à°ªà±à°°à°¾à°°à°‚à°à°¿à°‚à°šà°‚à°¡à°¿", + "app.poll.activePollInstruction": "మీ పోలà±â€Œà°•à± à°ªà±à°°à°¤à±à°¯à°•à±à°· à°ªà±à°°à°¤à°¿à°¸à±à°ªà°‚దనలనౠచూడటానికి à°ˆ à°ªà±à°¯à°¾à°¨à±†à°²à±â€Œà°¨à± తెరిచి ఉంచండి. మీరౠసిదà±à°§à°‚à°—à°¾ ఉనà±à°¨à°ªà±à°ªà±à°¡à±, ఫలితాలనౠపà±à°°à°šà±à°°à°¿à°‚చడానికి మరియౠపోలౠమà±à°—ించడానికి 'పోలింగౠఫలితాలనౠపà±à°°à°šà±à°°à°¿à°‚à°šà°‚à°¡à°¿' à°Žà°‚à°šà±à°•ోండి.", + "app.poll.publishLabel": "పోలౠఫలితాలనౠచూపà±à°¤à±‹à°‚ది", + "app.poll.backLabel": "పోలింగౠఎంపికలకౠతిరిగి వెళà±à°³à±", + "app.poll.closeLabel": "మూసివేయి", + "app.poll.waitingLabel": "à°ªà±à°°à°¤à°¿à°¸à±à°ªà°‚దనల కోసం వేచి ఉంది ({0} / {1})", + "app.poll.ariaInputCount": "పోలౠఎంపిక {1} యొకà±à°• {0}", + "app.poll.customPlaceholder": "పోలౠఎంపికనౠజోడించండి", + "app.poll.noPresentationSelected": "à°ªà±à°°à°¦à°°à±à°¶à°¨ à°à°¦à±€ à°Žà°‚à°šà±à°•ోబడలేదà±! దయచేసి ఒకదానà±à°¨à°¿ à°Žà°‚à°šà±à°•ోండి.", + "app.poll.clickHereToSelect": "à°Žà°‚à°šà±à°•ోవడానికి ఇకà±à°•à°¡ à°•à±à°²à°¿à°•ౠచేయండి", + "app.poll.t": "à°’à°ªà±à°ªà±", + "app.poll.f": "తపà±à°ªà±", + "app.poll.tf": "à°’à°ªà±à°ªà±/తపà±à°ªà±", + "app.poll.y": "à°…à°µà±à°¨à±", + "app.poll.n": "కాదà±", + "app.poll.yn": "à°…à°µà±à°¨à±/కాదà±", + "app.poll.a2": "A/B", + "app.poll.a3": "A / B / C", + "app.poll.a4": "A / B / C / D", + "app.poll.a5": "A / B / C / D / E", + "app.poll.answer.true": "à°’à°ªà±à°ªà±", + "app.poll.answer.false": "తపà±à°ªà±", + "app.poll.answer.yes": "à°…à°µà±à°¨à±", + "app.poll.answer.no": "కాదà±", + "app.poll.answer.a": "A", + "app.poll.answer.b": "B", + "app.poll.answer.c": "C", + "app.poll.answer.d": "D", + "app.poll.answer.e": "E", + "app.poll.liveResult.usersTitle": "వినియోగదారà±à°²à±", + "app.poll.liveResult.responsesTitle": "à°¸à±à°ªà°‚దన", + "app.polling.pollingTitle": "పోలింగౠఎంపికలà±", + "app.polling.pollAnswerLabel": "పోలౠసమాధానం {0}", + "app.polling.pollAnswerDesc": "{0} కౠఓటౠవేయడానికి à°ˆ ఎంపికనౠఎంచà±à°•ోండి", + "app.failedMessage": "à°•à±à°·à°®à°¾à°ªà°£à°²à±, సరà±à°µà°°à±â€Œà°•ౠకనెకà±à°Ÿà± చేయడంలో ఇబà±à°¬à°‚ది.", + "app.downloadPresentationButton.label": "అసలౠపà±à°°à°¦à°°à±à°¶à°¨à°¨à± డౌనà±â€Œà°²à±‹à°¡à± చేయండి", + "app.connectingMessage": "కనెకà±à°Ÿà± à°…à°µà±à°¤à±‹à°‚ది ....", + "app.waitingMessage": "à°¡à°¿à°¸à±à°•నెకà±à°Ÿà±. {0} సెకనà±à°²à°²à±‹ తిరిగి కనెకà±à°Ÿà± చేయడానికి à°ªà±à°°à°¯à°¤à±à°¨à°¿à°¸à±à°¤à±‹à°‚ది ...", + "app.retryNow": "ఇపà±à°ªà±à°¡à±‡ మళà±à°²à±€ à°ªà±à°°à°¯à°¤à±à°¨à°¿à°‚à°šà°‚à°¡à°¿", + "app.muteWarning.label": "మిమà±à°®à°²à±à°¨à°¿ à°…à°¨à±â€Œà°®à±à°¯à±‚టౠచేయడానికి {0} à°•à±à°²à°¿à°•ౠచేయండి.", + "app.navBar.settingsDropdown.optionsLabel": "ఎంపికలà±", + "app.navBar.settingsDropdown.fullscreenLabel": "పూరà±à°¤à°¿ à°¸à±à°•à±à°°à±€à°¨à± చేయండి", + "app.navBar.settingsDropdown.settingsLabel": "సెటà±à°Ÿà°¿à°‚à°—à±à°²à±", + "app.navBar.settingsDropdown.aboutLabel": "à°—à±à°°à°¿à°‚à°šà°¿", + "app.navBar.settingsDropdown.leaveSessionLabel": "లాగౌటà±", + "app.navBar.settingsDropdown.exitFullscreenLabel": "పూరà±à°¤à°¿ à°¸à±à°•à±à°°à±€à°¨à± à°¨à±à°‚à°¡à°¿ నిషà±à°•à±à°°à°®à°¿à°‚à°šà°‚à°¡à°¿", + "app.navBar.settingsDropdown.fullscreenDesc": "సెటà±à°Ÿà°¿à°‚à°—à±à°² మెనౠపూరà±à°¤à°¿ à°¸à±à°•à±à°°à±€à°¨à±â€Œà°—à°¾ చేయండి", + "app.navBar.settingsDropdown.settingsDesc": "సాధారణ సెటà±à°Ÿà°¿à°‚à°—à±à°²à°¨à± మారà±à°šà°‚à°¡à°¿", + "app.navBar.settingsDropdown.aboutDesc": "à°•à±à°²à°¯à°¿à°‚à°Ÿà± à°—à±à°°à°¿à°‚à°šà°¿ సమాచారానà±à°¨à°¿ చూపించà±", + "app.navBar.settingsDropdown.leaveSessionDesc": "సమావేశానà±à°¨à°¿ వదిలివేయండి", + "app.navBar.settingsDropdown.exitFullscreenDesc": "పూరà±à°¤à°¿ à°¸à±à°•à±à°°à±€à°¨à± మోడౠనà±à°‚à°¡à°¿ నిషà±à°•à±à°°à°®à°¿à°‚à°šà°‚à°¡à°¿", + "app.navBar.settingsDropdown.hotkeysLabel": "కీబోరà±à°¡à± షారà±à°Ÿà± à°•à°Ÿà±", + "app.navBar.settingsDropdown.hotkeysDesc": "à°…à°‚à°¦à±à°¬à°¾à°Ÿà±à°²à±‹ ఉనà±à°¨ కీబోరà±à°¡à± షారà±à°Ÿà± కటౠజాబితా", + "app.navBar.settingsDropdown.helpLabel": "సహాయం", + "app.navBar.settingsDropdown.helpDesc": "వీడియో à°Ÿà±à°¯à±à°Ÿà±‹à°°à°¿à°¯à°²à±â€Œà°²à°•ౠవినియోగదారà±à°¨à°¿ లింకౠచేసà±à°¤à±à°‚ది (కొతà±à°¤ à°Ÿà±à°¯à°¾à°¬à±â€Œà°¨à± తెరà±à°¸à±à°¤à±à°‚ది)", + "app.navBar.settingsDropdown.endMeetingDesc": "à°ªà±à°°à°¸à±à°¤à±à°¤ సమావేశానà±à°¨à°¿ à°®à±à°—ించారà±", + "app.navBar.settingsDropdown.endMeetingLabel": "సమావేశం à°®à±à°—à°¿à°‚à°šà°‚à°¡à°¿", + "app.navBar.userListToggleBtnLabel": "వినియోగదారౠజాబితా టోగà±à°²à± చేయండి", + "app.navBar.toggleUserList.ariaLabel": "వినియోగదారà±à°²à± మరియౠసందేశాలౠటోగà±à°²à± చేసà±à°¤à°¾à°¯à°¿", + "app.navBar.toggleUserList.newMessages": "కొతà±à°¤ సందేశ నోటిఫికేషనà±â€Œà°¤à±‹", + "app.navBar.recording": "సెషనౠరికారà±à°¡à± చేయబడà±à°¤à±‹à°‚ది", + "app.navBar.recording.on": "రికారà±à°¡à°¿à°‚à°—à±", + "app.navBar.recording.off": "రికారà±à°¡à°¿à°‚à°—à± à°…à°µà±à°µà°Ÿà±à°²à±†à°¦à±", + "app.navBar.emptyAudioBrdige": "మైకà±à°°à±‹à°«à±‹à°¨à± ఆనౠలో లేదà±. à°ˆ రికారà±à°¡à°¿à°‚à°—à±â€Œà°•ౠఆడియోనౠజోడించడానికి మీ మైకà±à°°à±‹à°«à±‹à°¨à±â€Œà°¨à± షేరౠచేయండి.", + "app.leaveConfirmation.confirmLabel": "వదిలివేయండి", + "app.leaveConfirmation.confirmDesc": "మిమà±à°®à°²à±à°¨à°¿ సమావేశం à°¨à±à°‚à°¡à°¿ లాగౠఅవà±à°Ÿà± చేసà±à°¤à±à°‚ది", + "app.endMeeting.title": "సమావేశం à°®à±à°—à°¿à°‚à°šà°‚à°¡à°¿", + "app.endMeeting.description": "మీరౠపà±à°°à°¤à°¿ à°’à°•à±à°•à°°à°¿ కోసం à°ˆ సమావేశానà±à°¨à°¿ à°®à±à°—ించాలనà±à°•à±à°‚à°Ÿà±à°¨à±à°¨à°¾à°°à°¾? (వినియోగదారà±à°²à°‚దరూ à°¡à°¿à°¸à±à°•నెకà±à°Ÿà± చేయబడతారà±)?", + "app.endMeeting.yesLabel": "à°…à°µà±à°¨à±", + "app.endMeeting.noLabel": "కాదà±", + "app.about.title": "à°—à±à°°à°¿à°‚à°šà°¿", + "app.about.version": "à°•à±à°²à°¯à°¿à°‚టౠబిలà±à°¡à±:", + "app.about.copyright": "కాపీరైటà±:", + "app.about.confirmLabel": "సరే", + "app.about.confirmDesc": "సరే", + "app.about.dismissLabel": "à°°à°¦à±à°¦à± చేయి", + "app.about.dismissDesc": "à°•à±à°²à°¯à°¿à°‚టౠసమాచారం à°—à±à°°à°¿à°‚à°šà°¿ మూసివేయండి", + "app.actionsBar.changeStatusLabel": "à°¸à±à°¥à°¿à°¤à°¿à°¨à°¿ మారà±à°šà°‚à°¡à°¿", + "app.actionsBar.muteLabel": "à°®à±à°¯à±‚à°Ÿà± ", + "app.actionsBar.unmuteLabel": "à°…à°¨à±â€Œ à°®à±à°¯à±‚à°Ÿà±", + "app.actionsBar.camOffLabel": "కెమెరా ఆఫà±", + "app.actionsBar.raiseLabel": "పెంచడం", + "app.actionsBar.label": "à°šà°°à±à°¯à°² పటà±à°Ÿà±€", + "app.actionsBar.actionsDropdown.restorePresentationLabel": "à°ªà±à°°à°¦à°°à±à°¶à°¨à°¨à± à°ªà±à°¨à°°à±à°¦à±à°§à°°à°¿à°‚à°šà°‚à°¡à°¿", + "app.actionsBar.actionsDropdown.restorePresentationDesc": "à°ˆ బటనà±, à°ªà±à°°à°¦à°°à±à°¶à°¨ మూసివేయబడిన తరà±à°µà°¾à°¤ దానà±à°¨à°¿ à°ªà±à°¨à°°à±à°¦à±à°§à°°à°¿à°¸à±à°¤à±à°‚ది", + "app.screenshare.screenShareLabel" : "à°¸à±à°•à±à°°à±€à°¨à± షేరà±", + "app.submenu.application.applicationSectionTitle": "à°…à°ªà±à°²à°¿à°•ేషనà±", + "app.submenu.application.animationsLabel": "యానిమేషనà±à°²à±", + "app.submenu.application.fontSizeControlLabel": "ఫాంటౠపరిమాణం", + "app.submenu.application.increaseFontBtnLabel": "ఫాంటౠపరిమాణానà±à°¨à°¿ పెంచà±", + "app.submenu.application.decreaseFontBtnLabel": "ఫాంటౠపరిమాణానà±à°¨à°¿ తగà±à°—à°¿à°‚à°šà±", + "app.submenu.application.currentSize": "à°ªà±à°°à°¸à±à°¤à±à°¤à°‚ {0}", + "app.submenu.application.languageLabel": "à°…à°ªà±à°²à°¿à°•ేషనౠà°à°¾à°·", + "app.submenu.application.languageOptionLabel": "à°à°¾à°·à°¨à± à°Žà°‚à°šà±à°•ోండి", + "app.submenu.application.noLocaleOptionLabel": "à°šà±à°°à±à°•ైన à°¸à±à°¥à°¾à°¨à°¿à°•à±à°²à± లేరà±", + "app.submenu.notification.SectionTitle": "నోటిఫికేషనà±à°¸à±", + "app.submenu.notification.Desc": "మీకౠఎలా మరియౠà°à°®à°¿ తెలియజేయబడà±à°¤à±à°‚దో చెపà±à°ªà°‚à°¡à°¿", + "app.submenu.notification.audioAlertLabel": "ఆడియో హెచà±à°šà°°à°¿à°•à°²à±", + "app.submenu.notification.pushAlertLabel": "పాపపౠహెచà±à°šà°°à°¿à°•à°²à±", + "app.submenu.notification.messagesLabel": "చాటౠసందేశం", + "app.submenu.notification.userJoinLabel": "వినియోగదారà±à°¡à± చేరడo", + "app.submenu.audio.micSourceLabel": "మైకà±à°°à±‹à°«à±‹à°¨à± మూలం", + "app.submenu.audio.speakerSourceLabel": "à°¸à±à°ªà±€à°•రౠమూలం", + "app.submenu.audio.streamVolumeLabel": "మీ ఆడియో à°¸à±à°Ÿà±à°°à±€à°®à± వాలà±à°¯à±‚à°®à±", + "app.submenu.video.title": "వీడియో", + "app.submenu.video.videoSourceLabel": "మూలానà±à°¨à°¿ చూడండి", + "app.submenu.video.videoOptionLabel": "వీకà±à°·à°£ మూలానà±à°¨à°¿ à°Žà°‚à°šà±à°•ోండి", + "app.submenu.video.videoQualityLabel": "వీడియో నాణà±à°¯à°¤", + "app.submenu.video.qualityOptionLabel": "వీడియో నాణà±à°¯à°¤à°¨à± à°Žà°‚à°šà±à°•ోండి", + "app.submenu.video.participantsCamLabel": "పాలà±à°—ొనేవారి వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œà°²à°¨à± చూసà±à°¤à±à°¨à±à°¨à°¾à°°à±", + "app.settings.applicationTab.label": "à°…à°ªà±à°²à°¿à°•ేషనà±", + "app.settings.audioTab.label": "ఆడియో", + "app.settings.videoTab.label": "వీడియో", + "app.settings.usersTab.label": "పాలà±à°—ొనేవారà±", + "app.settings.main.label": "సెటà±à°Ÿà°¿à°‚à°—à±à°²à±", + "app.settings.main.cancel.label": "à°°à°¦à±à°¦à± చేయి", + "app.settings.main.cancel.label.description": "మారà±à°ªà±à°²à°¨à± వదిలివేసà±à°¤à±à°‚ది మరియౠసెటà±à°Ÿà°¿à°‚à°—à±â€Œà°² మెనà±à°¨à°¿ మూసివేసà±à°¤à±à°‚ది", + "app.settings.main.save.label": "సేవà±", + "app.settings.main.save.label.description": "మారà±à°ªà±à°²à°¨à± సేవౠచేసà±à°¤à±à°‚ది మరియౠసెటà±à°Ÿà°¿à°‚à°—à±à°² మెనà±à°¨à°¿ మూసివేసà±à°¤à±à°‚ది", + "app.settings.dataSavingTab.label": "డేటా పొదà±à°ªà±", + "app.settings.dataSavingTab.webcam": "వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œà°²à°¨à± à°ªà±à°°à°¾à°°à°‚à°à°¿à°‚à°šà°‚à°¡à°¿", + "app.settings.dataSavingTab.screenShare": "డెసà±à°•à±à°Ÿà°¾à°ªà± షేరింగౠనౠపà±à°°à°¾à°°à°‚à°à°¿à°‚à°šà°‚à°¡à°¿", + "app.settings.dataSavingTab.description": "మీ à°¬à±à°¯à°¾à°‚à°¡à±â€Œà°µà°¿à°¡à±à°¤à±â€Œà°¨à± సేవౠచేయడానికి à°ªà±à°°à°¸à±à°¤à±à°¤à°‚ à°ªà±à°°à°¦à°°à±à°¶à°¿à°‚చబడà±à°¤à±à°¨à±à°¨ వాటిని సరà±à°¦à±à°¬à°¾à°Ÿà± చేయండి.", + "app.settings.save-notification.label": "సెటà±à°Ÿà°¿à°‚à°—à±à°²à± సేవౠచేయబడà±à°¡à°¾à°¯à°¿", + "app.statusNotifier.lowerHands": "చేతà±à°²à°¨à± కిందకి దించారà±", + "app.statusNotifier.raisedHandsTitle": "చేతà±à°²à°¨à± పైకి à°Žà°¤à±à°¤à°¾à°°à±", + "app.statusNotifier.raisedHandDesc": "{0} వారౠచేయి పైకి à°Žà°¤à±à°¤à°¾à°°à±", + "app.statusNotifier.and": "మరియà±", + "app.switch.onLabel": "ఆనà±", + "app.switch.offLabel": "ఆఫà±", + "app.talkingIndicator.ariaMuteDesc" : "వినియోగదారà±à°¨à°¿ à°®à±à°¯à±‚టౠచేయడానికి à°Žà°‚à°šà±à°•ోండి", + "app.talkingIndicator.isTalking" : "{0} మాటà±à°²à°¾à°¡à±à°¤à±à°¨à±à°¨à°¾à°°à±", + "app.talkingIndicator.wasTalking" : "{0} మాటà±à°²à°¾à°¡à°Ÿà°‚ మానేసారà±", + "app.actionsBar.actionsDropdown.actionsLabel": "à°šà°°à±à°¯à°²à±", + "app.actionsBar.actionsDropdown.presentationLabel": "à°ªà±à°°à°¦à°°à±à°¶à°¨à°¨à± à°…à°ªà±â€Œà°²à±‹à°¡à± చేయండి", + "app.actionsBar.actionsDropdown.initPollLabel": "పోలౠపà±à°°à°¾à°°à°‚à°à°¿à°‚à°šà°‚à°¡à°¿", + "app.actionsBar.actionsDropdown.desktopShareLabel": "మీ à°¸à±à°•à±à°°à±€à°¨à±â€Œà°¨à± షేరౠచేయండి", + "app.actionsBar.actionsDropdown.lockedDesktopShareLabel": "à°¸à±à°•à±à°°à±€à°¨à± షేరౠలాకౠచేయబడింది", + "app.actionsBar.actionsDropdown.stopDesktopShareLabel": "మీ à°¸à±à°•à±à°°à±€à°¨à±â€Œà°¨à± షేరౠచేయడం ఆపివేయండి", + "app.actionsBar.actionsDropdown.presentationDesc": "మీ à°ªà±à°°à°¦à°°à±à°¶à°¨à°¨à± à°…à°ªà±â€Œà°²à±‹à°¡à± చేయండి", + "app.actionsBar.actionsDropdown.initPollDesc": "పోలౠపà±à°°à°¾à°°à°‚à°à°¿à°‚à°šà°‚à°¡à°¿", + "app.actionsBar.actionsDropdown.desktopShareDesc": "మీ à°¸à±à°•à±à°°à±€à°¨à±â€Œà°¨à± ఇతరà±à°²à°¤à±‹ పంచà±à°•ోండి", + "app.actionsBar.actionsDropdown.stopDesktopShareDesc": "మీ à°¸à±à°•à±à°°à±€à°¨à±â€Œà°¨à± షేరౠచేయడానà±à°¨à°¿ ఆపివేయండి", + "app.actionsBar.actionsDropdown.pollBtnLabel": "పోలౠపà±à°°à°¾à°°à°‚à°à°¿à°‚à°šà°‚à°¡à°¿", + "app.actionsBar.actionsDropdown.pollBtnDesc": "పోలౠపేనà±â€Œà°¨à± టోగà±à°²à± చేసà±à°¤à±à°‚ది", + "app.actionsBar.actionsDropdown.saveUserNames": "వినియోగదారà±à°² పేరà±à°²à°¨à± సేవౠచేయి", + "app.actionsBar.actionsDropdown.createBreakoutRoom": "à°¬à±à°°à±‡à°•à±à°…à°µà±à°Ÿà± à°—à°¦à±à°²à°¨à± సృషà±à°Ÿà°¿à°‚à°šà±", + "app.actionsBar.actionsDropdown.createBreakoutRoomDesc": "à°ªà±à°°à°¸à±à°¤à±à°¤ సమావేశానà±à°¨à°¿ విà°à°œà°¿à°‚చడానికి à°¬à±à°°à±‡à°•à±â€Œà°…à°µà±à°Ÿà±â€Œà°²à°¨à± సృషà±à°Ÿà°¿à°‚à°šà°‚à°¡à°¿", + "app.actionsBar.actionsDropdown.captionsLabel": "శీరà±à°·à°¿à°•లనౠవà±à°°à°¾à°¯à°‚à°¡à°¿", + "app.actionsBar.actionsDropdown.captionsDesc": "శీరà±à°·à°¿à°•à°² పేనà±â€Œà°¨à± టోగà±à°²à± చేసà±à°¤à±à°‚ది", + "app.actionsBar.actionsDropdown.takePresenter": "à°ªà±à°°à±†à°œà±†à°‚టరౠతీసà±à°•ోండి", + "app.actionsBar.actionsDropdown.takePresenterDesc": "కొతà±à°¤ à°ªà±à°°à±†à°œà±†à°‚à°Ÿà°°à±à°—à°¾ మీరే కేటాయించà±à°•ోండి", + "app.actionsBar.emojiMenu.statusTriggerLabel": "à°¸à±à°¥à°¿à°¤à°¿à°¨à°¿ సెటౠచేయండి", + "app.actionsBar.emojiMenu.awayLabel": "దూరంగా", + "app.actionsBar.emojiMenu.awayDesc": "మీ à°¸à±à°¥à°¿à°¤à°¿à°¨à°¿ దూరంగా మారà±à°šà°‚à°¡à°¿", + "app.actionsBar.emojiMenu.raiseHandLabel": "పెంచడం", + "app.actionsBar.emojiMenu.raiseHandDesc": "à°ªà±à°°à°¶à±à°¨ అడగడానికి మీ చేయి పైకెతà±à°¤à°‚à°¡à°¿", + "app.actionsBar.emojiMenu.neutralLabel": "నిశà±à°šà°¯à°‚ లేని", + "app.actionsBar.emojiMenu.neutralDesc": "మీ à°¸à±à°¥à°¿à°¤à°¿à°¨à°¿ నిశà±à°šà°¯à°‚ లేనిదిగా మారà±à°šà°‚à°¡à°¿", + "app.actionsBar.emojiMenu.confusedLabel": "అయోమయంలో", + "app.actionsBar.emojiMenu.confusedDesc": "మీ à°¸à±à°¥à°¿à°¤à°¿à°¨à°¿ అయోమయంలో à°•à°¿ మారà±à°šà°‚à°¡à°¿", + "app.actionsBar.emojiMenu.sadLabel": "విచారం", + "app.actionsBar.emojiMenu.sadDesc": "మీ à°¸à±à°¥à°¿à°¤à°¿à°¨à°¿ విచారంగా మారà±à°šà°‚à°¡à°¿", + "app.actionsBar.emojiMenu.happyLabel": "సంతోషం", + "app.actionsBar.emojiMenu.happyDesc": "మీ à°¸à±à°¥à°¿à°¤à°¿à°¨à°¿ సంతోషంగా మారà±à°šà°‚à°¡à°¿", + "app.actionsBar.emojiMenu.noneLabel": "à°¸à±à°Ÿà±‡à°Ÿà°¸à± ని à°•à±à°²à°¿à°¯à°°à± చేయండి", + "app.actionsBar.emojiMenu.noneDesc": "మీ à°¸à±à°¥à°¿à°¤à°¿à°¨à°¿ à°•à±à°²à°¿à°¯à°°à± చేయండి", + "app.actionsBar.emojiMenu.applauseLabel": "à°šà°ªà±à°ªà°Ÿà±à°²à±", + "app.actionsBar.emojiMenu.applauseDesc": "మీ à°¸à±à°¥à°¿à°¤à°¿à°¨à°¿ à°šà°ªà±à°ªà°Ÿà±à°²à±à°—à°¾ మారà±à°šà°‚à°¡à°¿", + "app.actionsBar.emojiMenu.thumbsUpLabel": "బాగà±à°‚ది", + "app.actionsBar.emojiMenu.thumbsUpDesc": "మీ à°¸à±à°¥à°¿à°¤à°¿à°¨à°¿ బాగà±à°‚దికి మారà±à°šà°‚à°¡à°¿", + "app.actionsBar.emojiMenu.thumbsDownLabel": "బాగాలేదà±", + "app.actionsBar.emojiMenu.thumbsDownDesc": "మీ à°¸à±à°¥à°¿à°¤à°¿à°¨à°¿ బాగాలేదౠగా మారà±à°šà°‚à°¡à°¿", + "app.actionsBar.currentStatusDesc": "à°ªà±à°°à°¸à±à°¤à±à°¤ à°¸à±à°¥à°¿à°¤à°¿ {0}", + "app.actionsBar.captions.start": "శీరà±à°·à°¿à°•లనౠచూడటం à°ªà±à°°à°¾à°°à°‚à°à°¿à°‚à°šà°‚à°¡à°¿", + "app.actionsBar.captions.stop": "శీరà±à°·à°¿à°•లనౠచూడటం ఆపండి", + "app.audioNotification.audioFailedError1001": "వెబà±â€Œà°¸à°¾à°•ెటౠడిసà±à°•నెకà±à°Ÿà± చేయబడింది (లోపం 1001)", + "app.audioNotification.audioFailedError1002": "వెబà±â€Œà°¸à°¾à°•ెటౠకనెకà±à°·à°¨à± చేయలేకపోయింది (లోపం 1002)", + "app.audioNotification.audioFailedError1003": "à°¬à±à°°à±Œà°œà°°à± వెబà±à°°à±Œà°œà°°à± వెరà±à°·à°¨à± కౠమదà±à°¦à°¤à± లేదౠ(లోపం 1003) ", + "app.audioNotification.audioFailedError1004": "కాలà±â€Œà°²à±‹ వైఫలà±à°¯à°‚ (కారణం = {0}) (లోపం 1004)", + "app.audioNotification.audioFailedError1005": "కాలౠఅనà±à°•ోకà±à°‚à°¡à°¾ à°®à±à°—ిసింది (లోపం 1005)", + "app.audioNotification.audioFailedError1006": "కాలౠసమయం à°®à±à°—ిసింది (లోపం 1006)", + "app.audioNotification.audioFailedError1007": "కనెకà±à°·à°¨à± వైఫలà±à°¯à°‚ (ICE లోపం 1007)", + "app.audioNotification.audioFailedError1008": "బదిలీ విఫలమైంది (లోపం 1008)", + "app.audioNotification.audioFailedError1009": "STUN / TURN సరà±à°µà°°à± సమాచారానà±à°¨à°¿ పొందలేకపోయామౠ(లోపం 1009)", + "app.audioNotification.audioFailedError1010": "కనెకà±à°·à°¨à± సంధి సమయం à°®à±à°—ిసింది (ICE లోపం 1010)", + "app.audioNotification.audioFailedError1011": "కనెకà±à°·à°¨à± సమయం à°®à±à°—ిసింది (ICE లోపం 1011)", + "app.audioNotification.audioFailedError1012": "కనెకà±à°·à°¨à± మూసివేయబడింది (ICE లోపం 1012)", + "app.audioNotification.audioFailedMessage": "మీ ఆడియో కనెకà±à°Ÿà± చేయడంలో విఫలమైంది", + "app.audioNotification.mediaFailedMessage": "à°¸à±à°°à°•à±à°·à°¿à°¤ మూలాలౠమాతà±à°°à°®à±‡ à°…à°¨à±à°®à°¤à°¿à°‚చబడà±à°¤à±à°¨à±à°¨à°‚à°¦à±à°¨ getUserMicMedia విఫలమైంది", + "app.audioNotification.closeLabel": "మూసివేయి", + "app.audioNotificaion.reconnectingAsListenOnly": "వీకà±à°·à°•à±à°² మైకà±à°°à±‹à°«à±‹à°¨à± లాకౠచేయబడింది, మీరౠవినడానికి మాతà±à°°à°®à±‡ కనెకà±à°Ÿà± à°…à°µà±à°¤à±à°¨à±à°¨à°¾à°°à±", + "app.breakoutJoinConfirmation.title": "à°¬à±à°°à±‡à°•à±à°…à°µà±à°Ÿà± గదిలో చేరండి", + "app.breakoutJoinConfirmation.message": "మీరౠచేరాలనà±à°•à±à°‚à°Ÿà±à°¨à±à°¨à°¾à°°à°¾", + "app.breakoutJoinConfirmation.confirmDesc": "మిమà±à°®à°²à±à°¨à°¿ à°¬à±à°°à±‡à°•à±à°…à°µà±à°Ÿà± గదికి చేరà±à°¸à±à°¤à±à°‚ది", + "app.breakoutJoinConfirmation.dismissLabel": "à°°à°¦à±à°¦à± చేయి", + "app.breakoutJoinConfirmation.dismissDesc": "మూసివేసà±à°¤à±à°‚ది మరియౠబà±à°°à±‡à°•à±à°…à°µà±à°Ÿà± గదిలో చేరడానà±à°¨à°¿ తిరసà±à°•à°°à°¿à°¸à±à°¤à±à°‚ది", + "app.breakoutJoinConfirmation.freeJoinMessage": "చేరడానికి à°¬à±à°°à±‡à°•à±à°…à°µà±à°Ÿà± గదిని à°Žà°‚à°šà±à°•ోండి", + "app.breakoutTimeRemainingMessage": "à°¬à±à°°à±‡à°•à±à°…à°µà±à°Ÿà± గది సమయం మిగిలి ఉంది: {0}", + "app.breakoutWillCloseMessage": "సమయం à°®à±à°—ిసింది. à°¬à±à°°à±‡à°•à±à°…à°µà±à°Ÿà± గది à°¤à±à°µà°°à°²à±‹ మూసివేయబడà±à°¤à±à°‚ది", + "app.calculatingBreakoutTimeRemaining": "మిగిలిన సమయానà±à°¨à°¿ లెకà±à°•à°¿à°¸à±à°¤à±‹à°‚ది ...", + "app.audioModal.ariaTitle": "ఆడియో మోడలà±â€Œà°²à±‹ చేరండి", + "app.audioModal.microphoneLabel": "మైకà±à°°à±‹à°«à±‹à°¨à±", + "app.audioModal.listenOnlyLabel": "వినడానికి మాతà±à°°à°®à±‡", + "app.audioModal.audioChoiceLabel": "మీరౠఆడియోలో ఎలా చేరాలనà±à°•à±à°‚à°Ÿà±à°¨à±à°¨à°¾à°°à±?", + "app.audioModal.iOSBrowser": "ఆడియో / వీడియో మదà±à°¦à°¤à± లేదà±", + "app.audioModal.iOSErrorDescription": "à°ˆ సమయంలో iOS కోసం Chrome లో ఆడియో మరియౠవీడియో మదà±à°¦à°¤à± లేదà±.", + "app.audioModal.iOSErrorRecommendation": "సఫారి iOS ని ఉపయోగించమని మేమౠసిఫారà±à°¸à± చేసà±à°¤à±à°¨à±à°¨à°¾à°®à±.", + "app.audioModal.audioChoiceDesc": "à°ˆ సమావేశంలో ఆడియోలో ఎలా చేరాలో à°Žà°‚à°šà±à°•ోండి", + "app.audioModal.unsupportedBrowserLabel": "మీరౠపూరà±à°¤à°¿à°—à°¾ మదà±à°¦à°¤à± లేని à°¬à±à°°à±Œà°œà°°à±â€Œà°¨à± ఉపయోగిసà±à°¤à±à°¨à±à°¨à°Ÿà±à°²à± కనిపిసà±à°¤à±‹à°‚ది. పూరà±à°¤à°¿ మదà±à°¦à°¤à± కోసం దయచేసి {0} లేదా {1}ఉపయోగించండి.", + "app.audioModal.closeLabel": "మూసివేయి", + "app.audioModal.yes": "à°…à°µà±à°¨à±", + "app.audioModal.no": "కాదà±", + "app.audioModal.yes.arialabel" : "à°ªà±à°°à°¤à°¿à°§à±à°µà°¨à°¿ వినపడà±à°¤à±à°‚à°¡à°¿", + "app.audioModal.no.arialabel" : "à°ªà±à°°à°¤à°¿à°§à±à°µà°¨à°¿ వినబడదà±", + "app.audioModal.echoTestTitle": "ఇది à°ªà±à°°à±ˆà°µà±‡à°Ÿà± à°ªà±à°°à°¤à°¿à°§à±à°µà°¨à°¿ పరీకà±à°·. కొనà±à°¨à°¿ మాటలౠమాటà±à°²à°¾à°¡à°‚à°¡à°¿. మీరౠఆడియో వినà±à°¨à°¾à°°à°¾?", + "app.audioModal.settingsTitle": "మీ ఆడియో సెటà±à°Ÿà°¿à°‚à°—à±â€Œà°²à°¨à± మారà±à°šà°‚à°¡à°¿", + "app.audioModal.helpTitle": "మీ మీడియా పరికరాలతో సమసà±à°¯ ఉంది", + "app.audioModal.helpText": "మీ మైకà±à°°à±‹à°«à±‹à°¨à±â€Œà°•ౠయాకà±à°¸à±†à°¸à± చేయడానికి మీరౠఅనà±à°®à°¤à°¿ ఇచà±à°šà°¾à°°à°¾? మీరౠఆడియోలో చేరడానికి à°ªà±à°°à°¯à°¤à±à°¨à°¿à°‚చినపà±à°ªà±à°¡à±, మీ మీడియా పరికర à°…à°¨à±à°®à°¤à±à°²à°¨à± à°…à°¡à±à°—à±à°¤à±à°¨à±à°¨à°ªà±à°ªà±à°¡à± డైలాగౠకనిపించాలని గమనించండి, దయచేసి ఆడియో సమావేశంలో చేరడానికి అంగీకరించండి. అలా కాకపోతే, మీ à°¬à±à°°à±Œà°œà°°à± సెటà±à°Ÿà°¿à°‚à°—à±à°²à°²à±‹ మీ మైకà±à°°à±‹à°«à±‹à°¨à± à°…à°¨à±à°®à°¤à±à°²à°¨à± మారà±à°šà°¡à°¾à°¨à°¿à°•à°¿ à°ªà±à°°à°¯à°¤à±à°¨à°¿à°‚à°šà°‚à°¡à°¿.", + "app.audioModal.help.noSSL": "à°ˆ పేజీ à°…à°¸à±à°°à°•à±à°·à°¿à°¤à°®à±ˆà°¨à°¦à°¿. మైకà±à°°à±‹à°«à±‹à°¨à± యాకà±à°¸à±†à°¸à± à°…à°¨à±à°®à°¤à°¿à°‚చబడటానికి పేజీ తపà±à°ªà°¨à°¿à°¸à°°à°¿à°—à°¾ HTTPS à°¦à±à°µà°¾à°°à°¾ అందించబడà±à°¤à±à°‚ది. దయచేసి సరà±à°µà°°à± నిరà±à°µà°¾à°¹à°•à±à°¡à°¿à°¨à°¿ సంపà±à°°à°¦à°¿à°‚à°šà°‚à°¡à°¿.", + "app.audioModal.help.macNotAllowed": "మీ Mac సిసà±à°Ÿà°®à± à°ªà±à°°à°¾à°§à°¾à°¨à±à°¯à°¤à°²à± మీ మైకà±à°°à±‹à°«à±‹à°¨à±â€Œà°•ౠయాకà±à°¸à±†à°¸à± నౠఅడà±à°¡à±à°•à±à°‚à°Ÿà±à°¨à±à°¨à°Ÿà±à°²à± కనిపిసà±à°¤à±‹à°‚ది. సిసà±à°Ÿà°®à± à°ªà±à°°à°¾à°§à°¾à°¨à±à°¯à°¤à°²à±> à°à°¦à±à°°à°¤ & గోపà±à°¯à°¤> గోపà±à°¯à°¤> మైకà±à°°à±‹à°«à±‹à°¨à± తెరిచి, మీరౠఉపయోగిసà±à°¤à±à°¨à±à°¨ à°¬à±à°°à±Œà°œà°°à± తనిఖీ చేయబడిందని ధృవీకరించండి.", + "app.audioModal.audioDialTitle": "మీ ఫోనà±â€Œà°¨à± ఉపయోగించి చేరండి", + "app.audioDial.audioDialDescription": "డయలà±", + "app.audioDial.audioDialConfrenceText": "మరియౠకానà±à°«à°°à±†à°¨à±à°¸à± పినౠనంబరà±â€Œà°¨à± నమోదౠచేయండి:", + "app.audioModal.autoplayBlockedDesc": "ఆడియో à°ªà±à°²à±‡ చేయడానికి మాకౠమీ à°…à°¨à±à°®à°¤à°¿ అవసరం.", + "app.audioModal.playAudio": "ఆడియో à°ªà±à°²à±‡ చేయండి", + "app.audioModal.playAudio.arialabel" : "ఆడియో à°ªà±à°²à±‡ చేయండి", + "app.audioDial.tipIndicator": "à°šà°¿à°Ÿà±à°•à°¾", + "app.audioDial.tipMessage": "మిమà±à°®à°²à±à°¨à°¿ మీరౠమà±à°¯à±‚à°Ÿà± / à°…à°¨à±â€Œà°®à±à°¯à±‚టౠచేయడానికి మీ ఫోనà±â€Œà°²à±‹à°¨à°¿ '0' కీని నొకà±à°•à°‚à°¡à°¿.", + "app.audioModal.connecting": "కనెకà±à°Ÿà± à°…à°µà±à°¤à±‹à°‚ది", + "app.audioModal.connectingEchoTest": "à°ªà±à°°à°¤à°¿à°§à±à°µà°¨à°¿ పరీకà±à°·à°•ౠకనెకà±à°Ÿà± à°…à°µà±à°¤à±‹à°‚ది", + "app.audioManager.joinedAudio": "మీరౠఆడియో సమావేశంలో చేరారà±", + "app.audioManager.joinedEcho": "మీరౠపà±à°°à°¤à°¿à°§à±à°µà°¨à°¿ పరీకà±à°·à°²à±‹ చేరారà±", + "app.audioManager.leftAudio": "మీరౠఆడియో సమావేశం à°¨à±à°‚à°¡à°¿ వదిలి వెళà±à°³à°¾à°°à±", + "app.audioManager.reconnectingAudio": "ఆడియోనౠతిరిగి కనెకà±à°Ÿà± చేయడానికి à°ªà±à°°à°¯à°¤à±à°¨à°¿à°¸à±à°¤à±‹à°‚ది", + "app.audioManager.genericError": "లోపం: లోపం సంà°à°µà°¿à°‚చింది, దయచేసి మళà±à°³à±€ à°ªà±à°°à°¯à°¤à±à°¨à°¿à°‚à°šà°‚à°¡à°¿", + "app.audioManager.connectionError": "లోపం: కనెకà±à°·à°¨à± లోపం", + "app.audioManager.requestTimeout": "లోపం: à°…à°à±à°¯à°°à±à°¥à°¨ సమయం à°®à±à°—ిసింది", + "app.audioManager.invalidTarget": "లోపం: చెలà±à°²à°¨à°¿ లకà±à°·à±à°¯à°¾à°¨à°¿à°•à°¿ à°à°¦à±ˆà°¨à°¾ à°…à°à±à°¯à°°à±à°¥à°¿à°‚చడానికి à°ªà±à°°à°¯à°¤à±à°¨à°¿à°‚చారà±", + "app.audioManager.mediaError": "లోపం: మీ మీడియా పరికరాలనౠపొందడంలో సమసà±à°¯ ఉంది", + "app.audio.joinAudio": "ఆడియోలో చేరండి", + "app.audio.leaveAudio": "ఆడియోనౠవదిలివేయండి", + "app.audio.enterSessionLabel": "సెషనà±â€Œà°¨à± నమోదౠచేయండి", + "app.audio.playSoundLabel": "à°ªà±à°²à±‡ సౌండà±", + "app.audio.backLabel": "వెనకà±à°•à°¿", + "app.audio.audioSettings.titleLabel": "మీ ఆడియో సెటà±à°Ÿà°¿à°‚à°—à±â€Œà°²à°¨à± à°Žà°‚à°šà±à°•ోండి", + "app.audio.audioSettings.descriptionLabel": "దయచేసి గమనించండి, మీ à°¬à±à°°à±Œà°œà°°à±â€Œà°²à±‹ డైలాగౠకనిపిసà±à°¤à±à°‚ది, మీ మైకà±à°°à±‹à°«à±‹à°¨à±â€Œà°¨à± షేరౠచేయడానà±à°¨à°¿ మీరౠఅంగీకరించాలి.", + "app.audio.audioSettings.microphoneSourceLabel": "మైకà±à°°à±‹à°«à±‹à°¨à± మూలం", + "app.audio.audioSettings.speakerSourceLabel": "à°¸à±à°ªà±€à°•రౠమూలం", + "app.audio.audioSettings.microphoneStreamLabel": "మీ ఆడియో à°¸à±à°Ÿà±à°°à±€à°®à± వాలà±à°¯à±‚à°®à±", + "app.audio.audioSettings.retryLabel": "మళà±à°²à±€ à°ªà±à°°à°¯à°¤à±à°¨à°¿à°‚à°šà°‚à°¡à°¿", + "app.audio.listenOnly.backLabel": "వెనకà±à°•à°¿", + "app.audio.listenOnly.closeLabel": "మూసివేయి", + "app.audio.permissionsOverlay.title": "మీ మైకà±à°°à±‹à°«à±‹à°¨à±â€Œà°•à°¿ à°…à°¨à±à°®à°¤à°¿ ఇవà±à°µà°‚à°¡à°¿ ", + "app.audio.permissionsOverlay.hint": "మీతో వాయిసౠకానà±à°«à°°à±†à°¨à±à°¸à±â€Œà°•ౠచేరడానికి మీ మీడియా పరికరాలనౠఉపయోగించడానికి మాకౠమీరౠఅనà±à°®à°¤à°¿à°‚చాలà±à°¸à°¿à°¨ అవసరం ఉంది :)", + "app.error.removed": "మీరౠసమావేశం à°¨à±à°‚à°¡à°¿ తొలగించబడà±à°¡à°¾à°°à±", + "app.error.meeting.ended": "మీరౠసమావేశం à°¨à±à°‚à°¡à°¿ లాగౠఅవà±à°Ÿà± à°…à°¯à±à°¯à°¾à°°à±", + "app.meeting.logout.duplicateUserEjectReason": "సమావేశంలో చేరడానికి à°ªà±à°°à°¯à°¤à±à°¨à°¿à°¸à±à°¤à±à°¨à±à°¨ నకిలీ వినియోగదారà±à°¡à±", + "app.meeting.logout.permissionEjectReason": "à°…à°¨à±à°®à°¤à°¿ ఉలà±à°²à°‚ఘన కారణంగా తొలగించబడింది", + "app.meeting.logout.ejectedFromMeeting": "మిమà±à°®à°²à±à°¨à°¿ సమావేశం à°¨à±à°‚à°¡à°¿ తొలగించారà±", + "app.meeting.logout.validateTokenFailedEjectReason": "అధికార టోకెనౠనౠధృవీకరించడంలో విఫలమైంది", + "app.meeting.logout.userInactivityEjectReason": "వినియోగదారà±à°¡à± à°Žà°•à±à°•à±à°µà°¸à±‡à°ªà± యాకà±à°Ÿà°¿à°µà± à°—à°¾ లేరà±", + "app.meeting-ended.rating.legendLabel": "à°…à°à°¿à°ªà±à°°à°¾à°¯ రేటింగà±", + "app.meeting-ended.rating.starLabel": "à°¸à±à°Ÿà°¾à°°à±", + "app.modal.close": "మూసివేయి", + "app.modal.close.description": "మారà±à°ªà±à°²à°¨à± విసà±à°®à°°à°¿à°¸à±à°¤à±à°‚ది మరియౠమోడలà±â€Œà°¨à± మూసివేసà±à°¤à±à°‚ది", + "app.modal.confirm": "పూరà±à°¤à°¿ à°…à°¯à±à°¯à°¿à°‚ది", + "app.modal.newTab": "(కొతà±à°¤ టాబౠతెరà±à°¸à±à°¤à±à°‚ది)", + "app.modal.confirm.description": "మారà±à°ªà±à°²à°¨à± ఆదా చేసà±à°¤à±à°‚ది మరియౠమోడలà±â€Œà°¨à± మూసివేసà±à°¤à±à°‚ది", + "app.dropdown.close": "మూసివేయి", + "app.error.400": "తపà±à°ªà±à°¡à± వినà±à°¨à°ªà°‚", + "app.error.401": "అనధికార", + "app.error.403": "మిమà±à°®à°²à±à°¨à°¿ సమావేశం à°¨à±à°‚à°¡à°¿ తొలగించారà±", + "app.error.404": "దొరకలేదà±", + "app.error.410": "సమావేశం à°®à±à°—ిసింది", + "app.error.500": "à°…à°¯à±à°¯à±‹, à°à°¦à±‹ తపà±à°ªà± జరిగింది", + "app.error.leaveLabel": "మళà±à°³à±€ లాగినౠఅవà±à°µà°‚à°¡à°¿", + "app.error.fallback.presentation.title": "లోపం సంà°à°µà°¿à°‚చింది", + "app.error.fallback.presentation.description": "ఇది లాగినౠచేయబడింది. దయచేసి పేజీని మళà±à°²à±€ లోడౠచేయడానికి à°ªà±à°°à°¯à°¤à±à°¨à°¿à°‚à°šà°‚à°¡à°¿.", + "app.error.fallback.presentation.reloadButton": "మళà±à°²à±€ లోడౠచేయి", + "app.guest.waiting": "చేరడానికి à°…à°¨à±à°®à°¤à°¿ కోసం వేచి ఉంది", + "app.userList.guest.waitingUsers": "వినియోగదారà±à°²à± వేచి ఉనà±à°¨à°¾à°°à±", + "app.userList.guest.waitingUsersTitle": "వినియోగదారà±à°¨à°¿ నిరà±à°µà°¹à°£", + "app.userList.guest.optionTitle": "పెండింగౠవినియోగదారà±à°²à°¨à± à°°à°¿à°µà±à°¯à± చేయండి", + "app.userList.guest.allowAllAuthenticated": "à°ªà±à°°à°®à°¾à°£à±€à°•à°°à°¿à°‚à°šà°¿à°¨ వినియోగదారà±à°²à°¨à± à°…à°¨à±à°®à°¤à°¿à°‚à°šà°‚à°¡à°¿", + "app.userList.guest.allowAllGuests": "అతిథà±à°²à°‚దరినీ à°…à°¨à±à°®à°¤à°¿à°‚à°šà°‚à°¡à°¿", + "app.userList.guest.allowEveryone": "అందరినీ à°…à°¨à±à°®à°¤à°¿à°‚à°šà°‚à°¡à°¿", + "app.userList.guest.denyEveryone": "à°ªà±à°°à°¤à°¿ à°’à°•à±à°•రినీ తిరసà±à°•à°°à°¿à°‚à°šà°‚à°¡à°¿", + "app.userList.guest.pendingUsers": "{0} పెండింగà±â€Œà°²à±‹ ఉనà±à°¨ వినియోగదారà±à°²à±", + "app.userList.guest.pendingGuestUsers": "{0 } అతిథి వినియోగదారà±à°²à± పెండింగà±â€Œà°²à±‹ ఉనà±à°¨à°¾à°°à±", + "app.userList.guest.pendingGuestAlert": "సెషనà±â€Œà°²à±‹ చేరారౠమరియౠమీ ఆమోదం కోసం వేచి ఉనà±à°¨à°¾à°°à±.", + "app.userList.guest.rememberChoice": "ఎంపిక à°—à±à°°à±à°¤à±à°‚à°šà±à°•ో", + "app.userList.guest.acceptLabel": "అంగీకరించà±", + "app.userList.guest.denyLabel": "తిరసà±à°•à°°à°¿à°‚à°šà±", + "app.user-info.title": "డైరెకà±à°Ÿà°°à±€ చూడండి", + "app.toast.breakoutRoomEnded": "à°¬à±à°°à±‡à°•à±à°…à°µà±à°Ÿà± గది à°®à±à°—ిసింది. దయచేసి ఆడియోలో తిరిగి చేరండి.", + "app.toast.chat.public": "à°•à±à°°à±Šà°¤à±à°¤ పబà±à°²à°¿à°•ౠచాటౠసందేశం", + "app.toast.chat.private": "à°•à±à°°à±Šà°¤à±à°¤ à°ªà±à°°à±ˆà°µà±‡à°Ÿà± చాటౠసందేశం", + "app.toast.chat.system": "సిసà±à°Ÿà°®à±", + "app.toast.clearedEmoji.label": "ఎమోజి à°¸à±à°¥à°¿à°¤à°¿ à°•à±à°²à°¿à°¯à°°à± చేయబడింది", + "app.toast.setEmoji.label": "ఎమోజి à°¸à±à°¥à°¿à°¤à°¿ {0 } కౠసెటౠచేయబడింది", + "app.toast.meetingMuteOn.label": "అందరూ à°®à±à°¯à±‚టౠచేయబడà±à°¡à°¾à°°à±", + "app.toast.meetingMuteOff.label": "మీటింగౠమà±à°¯à±‚టౠఆపివేయబడింది", + "app.notification.recordingStart": "à°ˆ సెషనౠఇపà±à°ªà±à°¡à± రికారà±à°¡à± చేయబడà±à°¤à±‹à°‚ది", + "app.notification.recordingStop": "à°ˆ సెషనౠరికారà±à°¡à± చేయబడదà±", + "app.notification.recordingPaused": "à°ˆ సెషనౠఇకపై రికారà±à°¡à± చేయబడదà±", + "app.notification.recordingAriaLabel": "రికారà±à°¡à± చేసిన సమయం", + "app.notification.userJoinPushAlert": "{0 } సెషనà±â€Œà°²à±‹ చేరారà±", + "app.submenu.notification.raiseHandLabel": "చేయి పైకెతà±à°¤à°‚à°¡à°¿", + "app.shortcut-help.title": "యాకà±à°¸à±†à°¸à± కీలౠఅందà±à°¬à°¾à°Ÿà±à°²à±‹ లేవà±", + "app.shortcut-help.accessKeyNotAvailable": "యాకà±à°¸à±†à°¸à± కీలౠఅందà±à°¬à°¾à°Ÿà±à°²à±‹ లేవà±", + "app.shortcut-help.comboLabel": "కాంబో", + "app.shortcut-help.functionLabel": "à°«à°‚à°•à±à°·à°¨à±", + "app.shortcut-help.closeLabel": "మూసివేయి", + "app.shortcut-help.closeDesc": "కీబోరà±à°¡à± షారà±à°Ÿà± à°•à°Ÿà± à°² మోడలà±â€Œà°¨à± మూసివేసà±à°¤à±à°‚ది", + "app.shortcut-help.openOptions": "à°Žà°¨à±à°¨à°¿à°•లౠతెరవండి", + "app.shortcut-help.toggleUserList": "వినియోగదారౠజాబితానౠటోగà±à°²à± చేయండి", + "app.shortcut-help.toggleMute": "à°®à±à°¯à±‚à°Ÿà± / à°…à°¨à±â€Œà°®à±à°¯à±‚à°Ÿà±", + "app.shortcut-help.togglePublicChat": "పబà±à°²à°¿à°•ౠచాటà±â€Œà°¨à± టోగà±à°²à± చేయండి (వినియోగదారౠజాబితా తెరిచి ఉండాలి)", + "app.shortcut-help.hidePrivateChat": "à°ªà±à°°à±ˆà°µà±‡à°Ÿà± చాటౠదాచండి", + "app.shortcut-help.closePrivateChat": "à°ªà±à°°à±ˆà°µà±‡à°Ÿà± చాటà±â€Œà°¨à± మూసివేయండి", + "app.shortcut-help.openActions": "à°šà°°à±à°¯à°² మెనà±à°¨à°¿ తెరవండి", + "app.shortcut-help.openStatus": "à°¸à±à°¥à°¿à°¤à°¿ మెనà±à°¨à°¿ తెరవండి", + "app.shortcut-help.togglePan": "పానౠసాధనానà±à°¨à°¿ యాకà±à°Ÿà°¿à°µà±‡à°Ÿà± చేయండి (à°ªà±à°°à±†à°œà±†à°‚à°Ÿà°°à±)", + "app.shortcut-help.nextSlideDesc": "తదà±à°ªà°°à°¿ à°¸à±à°²à°¯à°¿à°¡à± (à°ªà±à°°à±†à°œà±†à°‚à°Ÿà°°à±)", + "app.shortcut-help.previousSlideDesc": "à°®à±à°¨à±à°ªà°Ÿà°¿ à°¸à±à°²à°¯à°¿à°¡à± (à°ªà±à°°à±†à°œà±†à°‚à°Ÿà°°à±)", + "app.lock-viewers.title": "వీకà±à°·à°•à±à°²à°¨à± లాకౠచేయి", + "app.lock-viewers.description": "à°ˆ ఎంపికలౠనిరà±à°¦à°¿à°·à±à°Ÿ ఎంపికలనౠఉపయోగించకà±à°‚à°¡à°¾ వీకà±à°·à°•à±à°²à°¨à± పరిమితం చేయడానికి మిమà±à°®à°²à±à°¨à°¿ à°…à°¨à±à°®à°¤à°¿à°¸à±à°¤à±à°‚ది.", + "app.lock-viewers.featuresLable": "లకà±à°·à°£à°®à±", + "app.lock-viewers.lockStatusLabel": "à°¸à±à°¥à°¿à°¤à°¿", + "app.lock-viewers.webcamLabel": "వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œà°¨à± షేరౠచేయండి", + "app.lock-viewers.otherViewersWebcamLabel": "ఇతర వీకà±à°·à°•à±à°² వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œà°²à°¨à± చూడండి", + "app.lock-viewers.microphoneLable": "మైకà±à°°à±‹à°«à±‹à°¨à± నౠషేరౠచేయండి", + "app.lock-viewers.PublicChatLabel": "పబà±à°²à°¿à°•ౠచాటౠసందేశాలనౠపంపండి", + "app.lock-viewers.PrivateChatLable": "à°ªà±à°°à±ˆà°µà±‡à°Ÿà± చాటౠసందేశాలనౠపంపండి", + "app.lock-viewers.notesLabel": "షేరà±à°¡à± నోటà±à°¸à±à°¨à± మారà±à°šà°‚à°¡à°¿", + "app.lock-viewers.userListLabel": "వినియోగదారà±à°² జాబితాలో ఇతర వీకà±à°·à°•à±à°²à°¨à± చూడండి", + "app.lock-viewers.ariaTitle": "వీకà±à°·à°•à±à°² సెటà±à°Ÿà°¿à°‚à°—à±â€Œà°² మోడలà±â€Œà°¨à± లాకౠచేయండి", + "app.lock-viewers.button.apply": "అమలౠచేయి", + "app.lock-viewers.button.cancel": "à°°à°¦à±à°¦à± చేయి", + "app.lock-viewers.locked": "లాకౠచేయబడింది", + "app.lock-viewers.unlocked": "అనౠలాకౠచేయబడింది", + "app.connection-status.ariaTitle": "కనెకà±à°·à°¨à± à°¸à±à°¥à°¿à°¤à°¿ మోడలà±", + "app.connection-status.title": "కనెకà±à°·à°¨à± à°¸à±à°¥à°¿à°¤à°¿", + "app.connection-status.description": "వినియోగదారà±à°² కనెకà±à°·à°¨à± à°¸à±à°¥à°¿à°¤à°¿à°¨à°¿ చూడండి", + "app.connection-status.empty": "కనెకà±à°Ÿà°¿à°µà°¿à°Ÿà±€ సమసà±à°¯ à°à°¦à±€ ఇపà±à°ªà°Ÿà°¿ వరకౠనివేదించబడలేదà±", + "app.connection-status.more": "మరినà±à°¨à°¿", + "app.connection-status.offline": "ఆఫà±à°²à±†à±–à°¨à±", + "app.recording.startTitle": "రికారà±à°¡à°¿à°‚à°—à± à°ªà±à°°à°¾à°°à°‚à°à°¿à°‚à°šà±", + "app.recording.stopTitle": "రికారà±à°¡à°¿à°‚à°—à±â€Œà°¨à± పాజౠచేయి", + "app.recording.resumeTitle": "రికారà±à°¡à°¿à°‚à°—à±â€Œà°¨à± తిరిగి à°ªà±à°°à°¾à°°à°‚à°à°¿à°‚à°šà±", + "app.recording.startDescription": "రికారà±à°¡à°¿à°‚à°—à±â€Œà°¨à± పాజౠచేయడానికి మీరౠతరà±à°µà°¾à°¤ మళà±à°²à±€ రికారà±à°¡à± బటనà±â€Œà°¨à± à°Žà°‚à°šà±à°•ోవచà±à°šà±.", + "app.recording.stopDescription": "మీరౠరికారà±à°¡à°¿à°‚à°—à±â€Œà°¨à± పాజౠచేయాలనà±à°•à±à°‚à°Ÿà±à°¨à±à°¨à°¾à°°à°¾? రికారà±à°¡à± బటనà±â€Œà°¨à± మళà±à°²à±€ à°Žà°‚à°šà±à°•ోవడం à°¦à±à°µà°¾à°°à°¾ మీరౠతిరిగి à°ªà±à°°à°¾à°°à°‚à°à°¿à°‚చవచà±à°šà±.", + "app.videoPreview.cameraLabel": "కెమెరా", + "app.videoPreview.profileLabel": "లకà±à°·à°£à°®à±", + "app.videoPreview.cancelLabel": "à°°à°¦à±à°¦à± చేయి", + "app.videoPreview.closeLabel": "మూసివేయి", + "app.videoPreview.findingWebcamsLabel": "వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œà°²à°¨à± à°•à°¨à±à°—ొనడం", + "app.videoPreview.startSharingLabel": "షేరౠచేయడం à°ªà±à°°à°¾à°°à°‚à°à°¿à°‚à°šà°‚à°¡à°¿", + "app.videoPreview.stopSharingLabel": "షేరౠచేయడం ఆపివేయండి", + "app.videoPreview.stopSharingAllLabel": "à°…à°¨à±à°¨à±€ ఆపండి", + "app.videoPreview.sharedCameraLabel": "à°ˆ కెమెరా ఇపà±à°ªà°Ÿà°¿à°•ే షేరౠచేయబడà±à°¤à±‹à°‚ది", + "app.videoPreview.webcamOptionLabel": "వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œà°¨à± à°Žà°‚à°šà±à°•ోండి", + "app.videoPreview.webcamPreviewLabel": "వెబà±â€Œà°•à±à°¯à°¾à°®à± à°ªà±à°°à°¿à°µà±à°¯à±‚", + "app.videoPreview.webcamSettingsTitle": "వెబà±â€Œà°•à±à°¯à°¾à°®à± సెటà±à°Ÿà°¿à°‚à°—à±à°²à±", + "app.videoPreview.webcamNotFoundLabel": "వెబà±â€Œà°•à±à°¯à°¾à°®à± à°•à°¨à±à°—ొనబడలేదà±", + "app.videoPreview.profileNotFoundLabel": "కెమెరా à°ªà±à°°à±Šà°«à±ˆà°²à± కౠమదà±à°¦à°¤à± లేదà±", + "app.video.joinVideo": "వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œà°¨à± షేరౠచేయండి", + "app.video.leaveVideo": "వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œ నౠషేరౠచేయడం ఆపివేయండి", + "app.video.iceCandidateError": "ICE à°…à°à±à°¯à°°à±à°¥à°¿à°¨à°¿ జోడించడంలో లోపం", + "app.video.iceConnectionStateError": "కనెకà±à°·à°¨à± వైఫలà±à°¯à°‚ (ICE లోపం 1107)", + "app.video.permissionError": "వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œà°¨à± షేరౠచేయడంలో లోపం. దయచేసి à°…à°¨à±à°®à°¤à±à°²à°¨à± పరిశీలించండి", + "app.video.sharingError": "వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œà°¨à± షేరౠచేయడంలో లోపం", + "app.video.notFoundError": "వెబà±â€Œà°•à±à°¯à°¾à°®à± à°•à°¨à±à°—ొనబడలేదà±. దయచేసి ఇది కనెకà±à°Ÿà± à°…à°¯à±à°¯à°¿à°‚దని నిరà±à°§à°¾à°°à°¿à°‚à°šà±à°•ోండి", + "app.video.notAllowed": "షేరౠవెబà±â€Œà°•à±à°¯à°¾à°®à± కోసం à°…à°¨à±à°®à°¤à°¿ లేదà±, దయచేసి మీ à°¬à±à°°à±Œà°œà°°à± à°…à°¨à±à°®à°¤à±à°²à°¨à± నిరà±à°§à°¾à°°à°¿à°‚à°šà±à°•ోండి", + "app.video.notSupportedError": "వెబà±â€Œà°•à±à°¯à°¾à°®à± వీడియోనౠసà±à°°à°•à±à°·à°¿à°¤ మూలాలతో మాతà±à°°à°®à±‡ షేరౠచేయవచà±à°šà±, మీ SSL సరà±à°Ÿà°¿à°«à°¿à°•ెటౠచెలà±à°²à±à°¬à°¾à°Ÿà± à°…à°¯à±à°¯à±‡à°²à°¾ చూసà±à°•ోండి", + "app.video.notReadableError": "వెబà±â€Œà°•à±à°¯à°¾à°®à± వీడియో పొందలేకపోయామà±. దయచేసి మరొక à°ªà±à°°à±‹à°—à±à°°à°¾à°®à± ,వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œà°¨à± ఉపయోగించడం లేదని నిరà±à°§à°¾à°°à°¿à°‚à°šà±à°•ోండి", + "app.video.mediaFlowTimeout1020": "మీడియా సరà±à°µà°°à±â€Œà°•ౠచేరà±à°•ోలేదౠ(లోపం 1020)", + "app.video.suggestWebcamLock": "వీకà±à°·à°•à±à°² వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œà°•ౠలాకౠసెటà±à°Ÿà°¿à°‚à°—à±â€Œà°¨à± అమలౠచేయాలా?", + "app.video.suggestWebcamLockReason": "(ఇది సమావేశం యొకà±à°• à°¸à±à°¥à°¿à°°à°¤à±à°µà°¾à°¨à±à°¨à°¿ మెరà±à°—à±à°ªà°°à±à°¸à±à°¤à±à°‚ది)", + "app.video.enable": "ఆనà±", + "app.video.cancel": "à°°à°¦à±à°¦à± చేయి", + "app.video.swapCam": "మారà±à°ªà°¿à°¡à°¿", + "app.video.swapCamDesc": "వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œà°² దిశనౠమారà±à°šà°‚à°¡à°¿", + "app.video.videoLocked": "వెబà±â€Œà°•à±à°¯à°¾à°®à± షేరింగౠ,లాకౠచేయబడింది", + "app.video.videoButtonDesc": "వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œà°¨à± షేరౠచేయండి", + "app.video.videoMenu": "వీడియో మెనà±", + "app.video.videoMenuDisabled": "వీడియో మెనౠసెటà±à°Ÿà°¿à°‚à°—à±â€Œà°²à°²à±‹ వెబà±â€Œà°•à±à°¯à°¾à°®à± నిలిపివేయబడింది", + "app.video.videoMenuDesc": "వీడియో మెనౠడà±à°°à°¾à°ªà±â€Œà°¡à±Œà°¨à± తెరవండి", + "app.video.chromeExtensionError": "మీరౠతపà±à°ªà°¨à°¿à°¸à°°à°¿à°—à°¾ ఇనà±â€Œà°¸à±à°Ÿà°¾à°²à± చేయాలి", + "app.video.chromeExtensionErrorLink": "à°ˆ Chrome పొడిగింపà±", + "app.fullscreenButton.label": "పూరà±à°¤à°¿ à°¸à±à°•à±à°°à±€à°¨à±{0} చేయండి", + "app.deskshare.iceConnectionStateError": "à°¸à±à°•à±à°°à±€à°¨à±â€Œà°¨à± పంచà±à°•à±à°¨à±‡à°Ÿà°ªà±à°ªà±à°¡à± కనెకà±à°·à°¨à± విఫలమైంది (ICE లోపం 1108)", + "app.sfu.mediaServerConnectionError2000": "మీడియా సరà±à°µà°°à±â€Œà°•ౠకనెకà±à°Ÿà± చేయడం సాధà±à°¯à°‚ కాలేదౠ(లోపం 2000)", + "app.sfu.mediaServerOffline2001": "మీడియా సరà±à°µà°°à± ఆఫà±â€Œà°²à±ˆà°¨à±â€Œà°²à±‹ ఉంది. దయచేసి తరà±à°µà°¾à°¤ మళà±à°³à±€ à°ªà±à°°à°¯à°¤à±à°¨à°¿à°‚à°šà°‚à°¡à°¿ (లోపం 2001)", + "app.sfu.mediaServerNoResources2002": "మీడియా సరà±à°µà°°à±â€Œà°•à± à°…à°‚à°¦à±à°¬à°¾à°Ÿà±à°²à±‹ ఉనà±à°¨ వనరà±à°²à± లేవౠ(లోపం 2002)", + "app.sfu.mediaServerRequestTimeout2003": "మీడియా సరà±à°µà°°à± à°…à°à±à°¯à°°à±à°¥à°¨à°²à± సమయం à°®à±à°—ిసింది (లోపం 2003)", + "app.sfu.serverIceGatheringFailed2021": "మీడియా సరà±à°µà°°à± కనెకà±à°·à°¨à± à°…à°à±à°¯à°°à±à°¥à±à°²à°¨à± సేకరించదౠ(ICE లోపం 2021)", + "app.sfu.serverIceGatheringFailed2022": "మీడియా సరà±à°µà°°à± కనెకà±à°·à°¨à± విఫలమైంది (ICE లోపం 2022)", + "app.sfu.mediaGenericError2200": "à°…à°à±à°¯à°°à±à°¥à°¨à°¨à± à°ªà±à°°à°¾à°¸à±†à°¸à± చేయడంలో మీడియా సరà±à°µà°°à± విఫలమైంది (లోపం 2200)", + "app.sfu.invalidSdp2202":"à°•à±à°²à°¯à°¿à°‚టౠచెలà±à°²à°¨à°¿ మీడియా à°…à°à±à°¯à°°à±à°¥à°¨à°¨à± రూపొందించారౠ(SDP లోపం 2202)", + "app.sfu.noAvailableCodec2203": "సరà±à°µà°°à± తగిన కోడెకà±â€Œà°¨à± à°•à°¨à±à°—ొనలేకపోయింది (లోపం 2203)", + "app.meeting.endNotification.ok.label": "సరే", + "app.whiteboard.annotations.poll": "పోలౠఫలితాలౠపà±à°°à°šà±à°°à°¿à°‚చబడà±à°¡à°¾à°¯à°¿", + "app.whiteboard.annotations.pollResult": "పోలౠఫలితం", + "app.whiteboard.toolbar.tools": "పరికరమà±à°²à±", + "app.whiteboard.toolbar.tools.hand": "పానà±", + "app.whiteboard.toolbar.tools.pencil": "పెనà±à°¸à°¿à°²à±", + "app.whiteboard.toolbar.tools.rectangle": "దీరà±à°˜ à°šà°¤à±à°°à°¸à±à°°à°‚", + "app.whiteboard.toolbar.tools.triangle": "à°¤à±à°°à°¿à°à±à°œà°®à±", + "app.whiteboard.toolbar.tools.ellipse": "దీరà±à°˜ వృతà±à°¤à°®à±", + "app.whiteboard.toolbar.tools.line": "లైనà±", + "app.whiteboard.toolbar.tools.text": "టెకà±à°Ÿà±à°¸à±", + "app.whiteboard.toolbar.thickness": "à°¡à±à°°à°¾à°¯à°¿à°‚గౠమందం", + "app.whiteboard.toolbar.thicknessDisabled": "à°¡à±à°°à°¾à°¯à°¿à°‚గౠమందం నిలిపివేయబడింది", + "app.whiteboard.toolbar.color": "à°°à°‚à°—à±à°²à±", + "app.whiteboard.toolbar.colorDisabled": "à°°à°‚à°—à±à°²à± నిలిపివేయబడà±à°¡à°¾à°¯à°¿", + "app.whiteboard.toolbar.color.black": "నలà±à°ªà± à°°à°‚à°—à±", + "app.whiteboard.toolbar.color.white": "తెలà±à°ªà± à°°à°‚à°—à±", + "app.whiteboard.toolbar.color.red": "à°Žà°°à±à°ªà± à°°à°‚à°—à±", + "app.whiteboard.toolbar.color.orange": "కమలాపండౠరంగà±", + "app.whiteboard.toolbar.color.eletricLime": "విదà±à°¯à±à°¤à± à°¸à±à°¨à±à°¨à°‚", + "app.whiteboard.toolbar.color.lime": "à°¸à±à°¨à±à°¨à°‚", + "app.whiteboard.toolbar.color.cyan": "ఆకాశ నీలం à°°à°‚à°—à±", + "app.whiteboard.toolbar.color.dodgerBlue": "మోసగాడౠనీలం", + "app.whiteboard.toolbar.color.blue": "నీలం", + "app.whiteboard.toolbar.color.violet": "ఊదా à°°à°‚à°—à±", + "app.whiteboard.toolbar.color.magenta": "మెజెంటా à°°à°‚à°—à±", + "app.whiteboard.toolbar.color.silver": "వెండి à°°à°‚à°—à±", + "app.whiteboard.toolbar.undo": "à°µà±à°¯à°¾à°–à±à°¯à°¾à°¨à°®à± à°°à°¦à±à°¦à± చేయండి", + "app.whiteboard.toolbar.clear": "à°…à°¨à±à°¨à°¿ à°µà±à°¯à°¾à°–à±à°¯à°¾à°¨à°¾à°²à°¨à± à°•à±à°²à°¿à°¯à°°à± చేయండి", + "app.whiteboard.toolbar.multiUserOn": "బహà±à°³-వినియోగదారి వైటà±â€Œà°¬à±‹à°°à±à°¡à±â€Œà°¨à± ఆనౠచేయండి", + "app.whiteboard.toolbar.multiUserOff": "బహà±à°³-వినియోగదారి వైటà±â€Œà°¬à±‹à°°à±à°¡à±â€Œà°¨à± ఆపివేయండి", + "app.whiteboard.toolbar.fontSize": "ఫాంటౠపరిమాణ జాబితా", + "app.feedback.title": "మీరౠసమావేశం à°¨à±à°‚à°¡à°¿ లాగౠఅవà±à°Ÿà± à°…à°¯à±à°¯à°¾à°°à±", + "app.feedback.subtitle": "బిగà±â€Œà°¬à±à°²à±‚బటనౠతో మీ à°…à°¨à±à°à°µà°‚ à°—à±à°°à°¿à°‚à°šà°¿ వినడానికి మేమౠఇషà±à°Ÿà°ªà°¡à°¤à°¾à°®à± (మీ ఇషà±à°Ÿ à°ªà±à°°à°•ారం ) .", + "app.feedback.textarea": "బిగà±â€Œà°¬à±à°²à±‚బటనà±â€Œà°¨à± ఎలా మెరà±à°—à±à°ªà°°à±à°¸à±à°¤à°¾à°®à±?", + "app.feedback.sendFeedback": "à°…à°à°¿à°ªà±à°°à°¾à°¯à°¾à°¨à±à°¨à°¿ పంపండి", + "app.feedback.sendFeedbackDesc": "à°…à°à°¿à°ªà±à°°à°¾à°¯à°¾à°¨à±à°¨à°¿ పంపండి మరియౠసమావేశానà±à°¨à°¿ వదిలివేయండి", + "app.videoDock.webcamFocusLabel": "దృషà±à°Ÿà°¿", + "app.videoDock.webcamFocusDesc": "à°Žà°‚à°šà±à°•à±à°¨à±à°¨ వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œà°ªà±ˆ దృషà±à°Ÿà°¿ పెటà±à°Ÿà°‚à°¡à°¿", + "app.videoDock.webcamUnfocusLabel": "దృషà±à°Ÿà°¿ లేని", + "app.videoDock.webcamUnfocusDesc": "à°Žà°‚à°šà±à°•à±à°¨à±à°¨ వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œà°¨à± ఫోకసౠచేయండి", + "app.videoDock.autoplayBlockedDesc": "ఇతర వినియోగదారà±à°² వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œà°²à°¨à± మీకౠచూపించడానికి మాకౠమీ à°…à°¨à±à°®à°¤à°¿ అవసరం.", + "app.videoDock.autoplayAllowLabel": "వెబà±â€Œà°•à±à°¯à°¾à°®à±â€Œà°²à°¨à± చూడండి", + "app.invitation.title": "à°¬à±à°°à±‡à°•à±à°…à°µà±à°Ÿà± గదికి ఆహà±à°µà°¾à°¨à°‚", + "app.invitation.confirm": "ఆహà±à°µà°¾à°¨à°¿à°‚à°šà°‚à°¡à°¿", + "app.createBreakoutRoom.title": "à°¬à±à°°à±‡à°•à±à°…à°µà±à°Ÿà± రూమà±à°²à±", + "app.createBreakoutRoom.ariaTitle": "à°¬à±à°°à±‡à°•à±à°…à°µà±à°Ÿà± à°—à°¦à±à°²à°¨à± దాచండి", + "app.createBreakoutRoom.breakoutRoomLabel": "à°¬à±à°°à±‡à°•à±à°…à°µà±à°Ÿà± రూమà±à°²à± {0}", + "app.createBreakoutRoom.generatingURL": "URL నౠసృషà±à°Ÿà°¿à°¸à±à°¤à±‹à°‚ది", + "app.createBreakoutRoom.generatedURL": "ఉతà±à°ªà°¤à±à°¤à°¿ చేసినది", + "app.createBreakoutRoom.duration": "à°µà±à°¯à°µà°§à°¿ {0}", + "app.createBreakoutRoom.room": "గది {0}", + "app.createBreakoutRoom.notAssigned": "కేటాయించబడలేదౠ({0})", + "app.createBreakoutRoom.join": " గదిలో చేరండి", + "app.createBreakoutRoom.joinAudio": "ఆడియోలో చేరండి", + "app.createBreakoutRoom.returnAudio": "ఆడియో కౠతిరిగి à°°à°‚à°¡à°¿", + "app.createBreakoutRoom.alreadyConnected": "గది లోనె ఉనà±à°¨à°¾à°°à±", + "app.createBreakoutRoom.confirm": "సృషà±à°Ÿà°¿à°‚à°šà°‚à°¡à°¿", + "app.createBreakoutRoom.record": "రికారà±à°¡à±", + "app.createBreakoutRoom.numberOfRooms": "à°—à°¦à±à°² సంఖà±à°¯", + "app.createBreakoutRoom.durationInMinutes": "à°µà±à°¯à°µà°§à°¿ (నిమిషాలà±)", + "app.createBreakoutRoom.randomlyAssign": "à°…à°¸à±à°¤à°µà±à°¯à°¸à±à°¤à°‚à°—à°¾ కేటాయించండి", + "app.createBreakoutRoom.endAllBreakouts": "à°…à°¨à±à°¨à°¿ à°¬à±à°°à±‡à°•à±à°…à°µà±à°Ÿà± à°—à°¦à±à°²à°¨à± à°®à±à°—à°¿à°‚à°šà°‚à°¡à°¿", + "app.createBreakoutRoom.roomName": "{0} (గది - {1})", + "app.createBreakoutRoom.doneLabel": "పూరà±à°¤à°¿ à°…à°¯à±à°¯à°¿à°‚ది", + "app.createBreakoutRoom.nextLabel": "తరà±à°µà°¾à°¤", + "app.createBreakoutRoom.minusRoomTime": "à°¬à±à°°à±‡à°•à±à°…à°µà±à°Ÿà± గది సమయానà±à°¨à°¿ తగà±à°—à°¿à°‚à°šà°‚à°¡à°¿", + "app.createBreakoutRoom.addRoomTime": "à°¬à±à°°à±‡à°•à±à°…à°µà±à°Ÿà± గది సమయానà±à°¨à°¿ పెంచండి", + "app.createBreakoutRoom.addParticipantLabel": "+ పాలà±à°—ొనేవారిని జోడించండి", + "app.createBreakoutRoom.freeJoin": "వినియోగదారà±à°²à± చేరడానికి à°’à°• à°¬à±à°°à±‡à°•à±à°…à°µà±à°Ÿà± గది à°Žà°‚à°šà±à°•ొవడానికి à°…à°¨à±à°®à°¤à°¿ ని ఇవà±à°µà°‚à°¡à°¿", + "app.createBreakoutRoom.leastOneWarnBreakout": "మీరౠకనీసం à°’à°• వినియోగదారà±à°¡à±à°¨à°¿ à°¬à±à°°à±‡à°•à±à°…à°µà±à°Ÿà± గదిలో ఉంచాలి.", + "app.createBreakoutRoom.modalDesc": "à°šà°¿à°Ÿà±à°•à°¾: మీరౠఒక నిరà±à°¦à°¿à°·à±à°Ÿ à°¬à±à°°à±‡à°•à±à°…à°µà±à°Ÿà± గదికి కేటాయించడానికి వినియోగదారౠపేరà±à°¨à± à°¡à±à°°à°¾à°—ౠమరియౠడà±à°°à°¾à°ªà± చేయండి.", + "app.createBreakoutRoom.roomTime": "{0}నిమిషాలà±", + "app.createBreakoutRoom.numberOfRoomsError": "à°—à°¦à±à°² సంఖà±à°¯ చెలà±à°²à°¦à±.", + "app.externalVideo.start": "కొతà±à°¤ వీడియోనౠషేరౠచేయండి", + "app.externalVideo.title": "బాహà±à°¯ వీడియోనౠషేరౠచేయండి", + "app.externalVideo.input": "బాహà±à°¯ వీడియో URL", + "app.externalVideo.urlInput": "వీడియో URL ని జోడించండి", + "app.externalVideo.urlError": "à°ˆ వీడియో URL à°•à°¿ మదà±à°¦à°¤à± లేదà±", + "app.externalVideo.close": "మూసివేయి", + "app.externalVideo.autoPlayWarning": "మీడియా సమకాలీకరణనౠపà±à°°à°¾à°°à°‚à°à°¿à°‚చడానికి వీడియోనౠపà±à°²à±‡ చేయండి", + "app.network.connection.effective.slow": "కనెకà±à°Ÿà°¿à°µà°¿à°Ÿà±€ సమసà±à°¯à°²à°¨à± మేమౠగమనిసà±à°¤à±à°¨à±à°¨à°¾à°®à±", + "app.network.connection.effective.slow.help": "మరింత సమాచారం", + "app.externalVideo.noteLabel": "గమనిక: షేరౠచేసిన బాహà±à°¯ వీడియోలౠరికారà±à°¡à°¿à°‚à°—à±â€Œà°²à±‹ కనిపించవà±. యూటà±à°¯à±‚à°¬à±, విమియో, ఇనà±â€Œà°¸à±à°Ÿà±à°°à°•à±à°šà°°à± మీడియా, à°Ÿà±à°µà°¿à°šà±, డైలీమోషనౠమరియౠమీడియా ఫైలౠURL లకౠ(à°‰.దా: https://example.com/xy.mp4) మదà±à°¦à°¤à± ఉంది.", + "app.actionsBar.actionsDropdown.shareExternalVideo": "బాహà±à°¯ వీడియోనౠషేరౠచేయండి", + "app.actionsBar.actionsDropdown.stopShareExternalVideo": "బాహà±à°¯ వీడియోనౠషేరౠచేయడం ఆపివేయండి", + "app.iOSWarning.label": "దయచేసి iOS 12.2 లేదా అంతకంటే à°Žà°•à±à°•à±à°µà°•à± à°…à°ªà±â€Œà°—à±à°°à±‡à°¡à± చేయండి", + "app.legacy.unsupportedBrowser": "మీరౠపూరà±à°¤à°¿à°—à°¾ మదà±à°¦à°¤à± లేని à°¬à±à°°à±Œà°œà°°à±â€Œà°¨à± ఉపయోగిసà±à°¤à±à°¨à±à°¨à°Ÿà±à°²à± కనిపిసà±à°¤à±‹à°‚ది. పూరà±à°¤à°¿ మదà±à°¦à°¤à± కోసం దయచేసి {0} లేదా {1}ఉపయోగించండి.", + "app.legacy.upgradeBrowser": "మీరౠమదà±à°¦à°¤à± ఉనà±à°¨ à°¬à±à°°à±Œà°œà°°à± యొకà±à°• పాత సంసà±à°•రణనౠఉపయోగిసà±à°¤à±à°¨à±à°¨à°Ÿà±à°²à± కనిపిసà±à°¤à±‹à°‚ది. పూరà±à°¤à°¿ మదà±à°¦à°¤à± కోసం దయచేసి మీ à°¬à±à°°à±Œà°œà°°à±â€Œà°¨à± à°…à°ªà±â€Œà°—à±à°°à±‡à°¡à± చేయండి.", + "app.legacy.criosBrowser": "IOS లో దయచేసి పూరà±à°¤à°¿ మదà±à°¦à°¤à± కోసం Safari ని ఉపయోగించండి." + +} + diff --git a/bigbluebutton-html5/private/locales/th.json b/bigbluebutton-html5/private/locales/th.json index e89ed27c66201f5655538743ed155e24fc7b394d..5d3c14adae348922e3b48d8c73bd4ec5e38bb75f 100644 --- a/bigbluebutton-html5/private/locales/th.json +++ b/bigbluebutton-html5/private/locales/th.json @@ -50,10 +50,10 @@ "app.note.title": "à¹à¸šà¹ˆà¸‡à¸›à¸±à¸™à¸‚้à¸à¸„วาม", "app.note.label": "ข้à¸à¸„วาม", "app.note.hideNoteLabel": "ซ่à¸à¸™à¸‚้à¸à¸„วาม", + "app.note.tipLabel": "à¸à¸” ESC สำหรับโฟà¸à¸±à¸ªà¹à¸–บเมนูà¹à¸à¹‰à¹„ข", "app.user.activityCheck": "ตรวจสà¸à¸šà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¸‡à¸²à¸™", "app.user.activityCheck.label": "ตรวจสà¸à¸šà¸§à¹ˆà¸²à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¸¢à¸±à¸‡à¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸à¸²à¸£à¸›à¸£à¸°à¸Šà¸¸à¸¡à¸«à¸£à¸·à¸à¹„ม่ ({0})", "app.user.activityCheck.check": "ตรวจสà¸à¸š", - "app.note.tipLabel": "à¸à¸” ESC สำหรับโฟà¸à¸±à¸ªà¹à¸–บเมนูà¹à¸à¹‰à¹„ข", "app.userList.usersTitle": "ผู้ใช้งาน", "app.userList.participantsTitle": "ผู้เข้าร่วม", "app.userList.messagesTitle": "ข้à¸à¸„วาม", @@ -121,8 +121,6 @@ "app.meeting.meetingTimeRemaining": "เหลืà¸à¹€à¸§à¸¥à¸²à¸›à¸£à¸°à¸Šà¸¸à¸¡: {0}", "app.meeting.meetingTimeHasEnded": "หมดเวลาà¹à¸¥à¹‰à¸§ à¸à¸²à¸£à¸›à¸£à¸°à¸Šà¸¸à¸¡à¸ˆà¸°à¸›à¸´à¸”ในไม่ช้า", "app.meeting.endedMessage": "คุณจะถูà¸à¸ªà¹ˆà¸‡à¸•่à¸à¸à¸¥à¸±à¸šà¹„ปที่หน้าจà¸à¸«à¸¥à¸±à¸", - "app.meeting.alertMeetingEndsUnderOneMinute": "à¸à¸²à¸£à¸›à¸£à¸°à¸Šà¸¸à¸¡à¸ˆà¸°à¸›à¸´à¸”ในเวลาไม่à¸à¸µà¹ˆà¸™à¸²à¸—ี", - "app.meeting.alertBreakoutEndsUnderOneMinute": "à¸à¸³à¸¥à¸±à¸‡à¸ˆà¸°à¸›à¸´à¸”ในหนึ่งนาที", "app.presentation.hide": "ซ่à¸à¸™à¸‡à¸²à¸™à¸™à¸³à¹€à¸ªà¸™à¸", "app.presentation.notificationLabel": "งานนำเสนà¸à¸›à¸±à¸ˆà¸ˆà¸¸à¸šà¸±à¸™", "app.presentation.slideContent": "เลื่à¸à¸™à¹€à¸™à¸·à¹‰à¸à¸«à¸²", @@ -259,7 +257,6 @@ "app.leaveConfirmation.confirmLabel": "à¸à¸à¸à¸ˆà¸²à¸", "app.leaveConfirmation.confirmDesc": "นำคุณà¸à¸à¸à¸ˆà¸²à¸à¸à¸²à¸£à¸›à¸£à¸°à¸Šà¸¸à¸¡", "app.endMeeting.title": "สิ้นสุดà¸à¸²à¸£à¸›à¸£à¸°à¸Šà¸¸à¸¡", - "app.endMeeting.description": "คุณà¹à¸™à¹ˆà¹ƒà¸ˆà¸«à¸£à¸·à¸à¸§à¹ˆà¸²à¸•้à¸à¸‡à¸à¸²à¸£à¸ªà¸´à¹‰à¸™à¸ªà¸¸à¸”เซสชันนี้", "app.endMeeting.yesLabel": "ใช่", "app.endMeeting.noLabel": "ไม่ใช่", "app.about.title": "เà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸š", @@ -280,10 +277,6 @@ "app.screenshare.screenShareLabel" : "à¹à¸Šà¸£à¹Œà¸«à¸™à¹‰à¸²à¸ˆà¸", "app.submenu.application.applicationSectionTitle": "à¹à¸à¸Ÿà¸¥à¸´à¹€à¸„ชั่น", "app.submenu.application.animationsLabel": "เคลื่à¸à¸™à¹„หว", - "app.submenu.application.audioAlertLabel": "เสียงà¹à¸ˆà¹‰à¸‡à¹€à¸•ืà¸à¸™à¸ªà¸³à¸«à¸£à¸±à¸šà¸à¸²à¸£à¹à¸Šà¸—", - "app.submenu.application.pushAlertLabel": "à¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•ืà¸à¸™à¸›à¹Šà¸à¸›à¸à¸±à¸žà¸ªà¸³à¸«à¸£à¸±à¸šà¸à¸²à¸£à¹à¸Šà¸—", - "app.submenu.application.userJoinAudioAlertLabel": "à¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•ืà¸à¸™à¸”้วยเสียงสำหรับผู้ใช้เข้าร่วม", - "app.submenu.application.userJoinPushAlertLabel": "à¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•ืà¸à¸™à¸›à¹Šà¸à¸›à¸à¸±à¸žà¸ªà¸³à¸«à¸£à¸±à¸šà¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¹€à¸‚้าร่วม", "app.submenu.application.fontSizeControlLabel": "ขนาดตัวà¸à¸±à¸à¸©à¸£", "app.submenu.application.increaseFontBtnLabel": "เพิ่มขนาดตัวà¸à¸±à¸à¸©à¸£à¸‚à¸à¸‡à¹à¸à¸›à¸žà¸¥à¸´à¹€à¸„ชัน", "app.submenu.application.decreaseFontBtnLabel": "ลดขนาดà¹à¸šà¸šà¸à¸±à¸à¸©à¸£à¸‚à¸à¸‡à¹à¸à¸›à¸žà¸¥à¸´à¹€à¸„ชัน", @@ -551,22 +544,8 @@ "app.video.videoMenuDesc": "เปิดเมนูวิดีโà¸à¹à¸šà¸šà¹€à¸¥à¸·à¹ˆà¸à¸™à¸¥à¸‡", "app.video.chromeExtensionError": "คุณต้à¸à¸‡à¸•ิดตั้ง", "app.video.chromeExtensionErrorLink": "ส่วนขยาย Chrome นี้", - "app.video.stats.title": "สถิติà¸à¸²à¸£à¹€à¸Šà¸·à¹ˆà¸à¸¡à¸•่à¸", - "app.video.stats.packetsReceived": "รับà¹à¸žà¹‡à¸„เà¸à¹‡à¸•", - "app.video.stats.packetsSent": "ส่งà¹à¸žà¹‡à¸„เà¸à¹‡à¸•", - "app.video.stats.packetsLost": "à¹à¸žà¹‡à¸„เà¸à¹‡à¸•ที่หายไป", - "app.video.stats.bitrate": "บิตเรท", - "app.video.stats.lostPercentage": "เปà¸à¸£à¹Œà¹€à¸‹à¹‡à¸™à¸•์ที่หายไปทั้งหมด", - "app.video.stats.lostRecentPercentage": "เปà¸à¸£à¹Œà¹€à¸‹à¹‡à¸™à¸•์ล่าสุดที่หายไป", - "app.video.stats.dimensions": "ขนาด", - "app.video.stats.codec": "Codec", - "app.video.stats.decodeDelay": "หน่วงเวลาà¸à¸²à¸£à¸–à¸à¸”รหัส", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "ใช้งานà¸à¸²à¸£à¹€à¸‚้ารหัส", - "app.video.stats.currentDelay": "หน่วงเวลาปัจจุบัน", "app.fullscreenButton.label": "à¸à¸³à¸«à¸™à¸” {0} เต็มหน้าจà¸", "app.meeting.endNotification.ok.label": "ตà¸à¸¥à¸‡", - "app.whiteboard.annotations.poll": "เผยà¹à¸žà¸£à¹ˆà¸œà¸¥à¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸„วามคิดเห็นà¹à¸¥à¹‰à¸§", "app.whiteboard.toolbar.tools": "เครื่à¸à¸‡à¸¡à¸·à¸", "app.whiteboard.toolbar.tools.hand": "à¹à¸–บ", "app.whiteboard.toolbar.tools.pencil": "ดินสà¸", @@ -647,7 +626,6 @@ "app.externalVideo.autoPlayWarning": "เล่นวิดีโà¸à¹€à¸žà¸·à¹ˆà¸à¹€à¸›à¸´à¸”ใช้งานà¸à¸²à¸£à¸‹à¸´à¸‡à¹‚ครไนซ์สื่à¸", "app.network.connection.effective.slow": "เราà¸à¸³à¸¥à¸±à¸‡à¸ªà¸±à¸‡à¹€à¸à¸•เห็นปัà¸à¸«à¸²à¸à¸²à¸£à¹€à¸Šà¸·à¹ˆà¸à¸¡à¸•่à¸", "app.network.connection.effective.slow.help": "ข้à¸à¸¡à¸¹à¸¥à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¸™à¸µà¹‰", - "app.externalVideo.noteLabel": "หมายเหตุ: วิดีโà¸à¸ ายนà¸à¸à¸—ี่à¹à¸Šà¸£à¹Œà¸ˆà¸°à¹„ม่ปราà¸à¸à¹ƒà¸™à¸à¸²à¸£à¸šà¸±à¸™à¸—ึภรà¸à¸‡à¸£à¸±à¸š YouTube, Vimeo, สื่à¸à¸à¸²à¸£à¸ªà¸à¸™, Twitch à¹à¸¥à¸° Daily Motion", "app.actionsBar.actionsDropdown.shareExternalVideo": "à¹à¸šà¹ˆà¸‡à¸›à¸±à¸™à¸§à¸µà¸”ีโà¸à¸ ายนà¸à¸", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "หยุดà¹à¸šà¹ˆà¸‡à¸›à¸±à¸™à¸§à¸µà¸”ีโà¸à¸ ายนà¸à¸", "app.iOSWarning.label": "à¸à¸£à¸¸à¸“าà¸à¸±à¸žà¹€à¸”ทเป็น iOS 12.2 หรืà¸à¸ªà¸¹à¸‡à¸à¸§à¹ˆà¸²", diff --git a/bigbluebutton-html5/private/locales/th_TH.json b/bigbluebutton-html5/private/locales/th_TH.json index f21e219988d7df54fdb5385f6c246e16311273aa..b847ceeb31acc318313b23ec64d95e1531e8b071 100644 --- a/bigbluebutton-html5/private/locales/th_TH.json +++ b/bigbluebutton-html5/private/locales/th_TH.json @@ -50,10 +50,10 @@ "app.note.title": "à¹à¸šà¹ˆà¸‡à¸›à¸±à¸™à¸‚้à¸à¸„วาม", "app.note.label": "ข้à¸à¸„วาม", "app.note.hideNoteLabel": "ซ่à¸à¸™à¸‚้à¸à¸„วาม", + "app.note.tipLabel": "à¸à¸” ESC สำหรับโฟà¸à¸±à¸ªà¹à¸–บเมนูà¹à¸à¹‰à¹„ข", "app.user.activityCheck": "ตรวจสà¸à¸šà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¸‡à¸²à¸™", "app.user.activityCheck.label": "ตรวจสà¸à¸šà¸§à¹ˆà¸²à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¸¢à¸±à¸‡à¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸à¸²à¸£à¸›à¸£à¸°à¸Šà¸¸à¸¡à¸«à¸£à¸·à¸à¹„ม่ ({0})", "app.user.activityCheck.check": "ตรวจสà¸à¸š", - "app.note.tipLabel": "à¸à¸” ESC สำหรับโฟà¸à¸±à¸ªà¹à¸–บเมนูà¹à¸à¹‰à¹„ข", "app.userList.usersTitle": "ผู้ใช้งาน", "app.userList.participantsTitle": "ผู้เข้าร่วม", "app.userList.messagesTitle": "ข้à¸à¸„วาม", @@ -121,8 +121,6 @@ "app.meeting.meetingTimeRemaining": "เหลืà¸à¹€à¸§à¸¥à¸²à¸›à¸£à¸°à¸Šà¸¸à¸¡: {0}", "app.meeting.meetingTimeHasEnded": "หมดเวลาà¹à¸¥à¹‰à¸§ à¸à¸²à¸£à¸›à¸£à¸°à¸Šà¸¸à¸¡à¸ˆà¸°à¸›à¸´à¸”ในไม่ช้า", "app.meeting.endedMessage": "คุณจะถูà¸à¸ªà¹ˆà¸‡à¸•่à¸à¸à¸¥à¸±à¸šà¹„ปที่หน้าจà¸à¸«à¸¥à¸±à¸", - "app.meeting.alertMeetingEndsUnderOneMinute": "à¸à¸²à¸£à¸›à¸£à¸°à¸Šà¸¸à¸¡à¸ˆà¸°à¸›à¸´à¸”ในเวลาไม่à¸à¸µà¹ˆà¸™à¸²à¸—ี", - "app.meeting.alertBreakoutEndsUnderOneMinute": "à¸à¸³à¸¥à¸±à¸‡à¸ˆà¸°à¸›à¸´à¸”ในหนึ่งนาที", "app.presentation.hide": "ซ่à¸à¸™à¸‡à¸²à¸™à¸™à¸³à¹€à¸ªà¸™à¸", "app.presentation.notificationLabel": "งานนำเสนà¸à¸›à¸±à¸ˆà¸ˆà¸¸à¸šà¸±à¸™", "app.presentation.slideContent": "เลื่à¸à¸™à¹€à¸™à¸·à¹‰à¸à¸«à¸²", @@ -255,7 +253,6 @@ "app.leaveConfirmation.confirmLabel": "à¸à¸à¸à¸ˆà¸²à¸", "app.leaveConfirmation.confirmDesc": "นำคุณà¸à¸à¸à¸ˆà¸²à¸à¸à¸²à¸£à¸›à¸£à¸°à¸Šà¸¸à¸¡", "app.endMeeting.title": "สิ้นสุดà¸à¸²à¸£à¸›à¸£à¸°à¸Šà¸¸à¸¡", - "app.endMeeting.description": "คุณà¹à¸™à¹ˆà¹ƒà¸ˆà¸«à¸£à¸·à¸à¸§à¹ˆà¸²à¸•้à¸à¸‡à¸à¸²à¸£à¸ªà¸´à¹‰à¸™à¸ªà¸¸à¸”เซสชันนี้", "app.endMeeting.yesLabel": "ใช่", "app.endMeeting.noLabel": "ไม่ใช่", "app.about.title": "เà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸š", @@ -276,10 +273,6 @@ "app.screenshare.screenShareLabel" : "à¹à¸Šà¸£à¹Œà¸«à¸™à¹‰à¸²à¸ˆà¸", "app.submenu.application.applicationSectionTitle": "à¹à¸à¸Ÿà¸¥à¸´à¹€à¸„ชั่น", "app.submenu.application.animationsLabel": "เคลื่à¸à¸™à¹„หว", - "app.submenu.application.audioAlertLabel": "เสียงà¹à¸ˆà¹‰à¸‡à¹€à¸•ืà¸à¸™à¸ªà¸³à¸«à¸£à¸±à¸šà¸à¸²à¸£à¹à¸Šà¸—", - "app.submenu.application.pushAlertLabel": "à¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•ืà¸à¸™à¸›à¹Šà¸à¸›à¸à¸±à¸žà¸ªà¸³à¸«à¸£à¸±à¸šà¸à¸²à¸£à¹à¸Šà¸—", - "app.submenu.application.userJoinAudioAlertLabel": "à¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•ืà¸à¸™à¸”้วยเสียงสำหรับผู้ใช้เข้าร่วม", - "app.submenu.application.userJoinPushAlertLabel": "à¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•ืà¸à¸™à¸›à¹Šà¸à¸›à¸à¸±à¸žà¸ªà¸³à¸«à¸£à¸±à¸šà¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¹€à¸‚้าร่วม", "app.submenu.application.fontSizeControlLabel": "ขนาดตัวà¸à¸±à¸à¸©à¸£", "app.submenu.application.increaseFontBtnLabel": "เพิ่มขนาดตัวà¸à¸±à¸à¸©à¸£à¸‚à¸à¸‡à¹à¸à¸›à¸žà¸¥à¸´à¹€à¸„ชัน", "app.submenu.application.decreaseFontBtnLabel": "ลดขนาดà¹à¸šà¸šà¸à¸±à¸à¸©à¸£à¸‚à¸à¸‡à¹à¸à¸›à¸žà¸¥à¸´à¹€à¸„ชัน", @@ -547,22 +540,8 @@ "app.video.videoMenuDesc": "เปิดเมนูวิดีโà¸à¹à¸šà¸šà¹€à¸¥à¸·à¹ˆà¸à¸™à¸¥à¸‡", "app.video.chromeExtensionError": "คุณต้à¸à¸‡à¸•ิดตั้ง", "app.video.chromeExtensionErrorLink": "ส่วนขยาย Chrome นี้", - "app.video.stats.title": "สถิติà¸à¸²à¸£à¹€à¸Šà¸·à¹ˆà¸à¸¡à¸•่à¸", - "app.video.stats.packetsReceived": "รับà¹à¸žà¹‡à¸„เà¸à¹‡à¸•", - "app.video.stats.packetsSent": "ส่งà¹à¸žà¹‡à¸„เà¸à¹‡à¸•", - "app.video.stats.packetsLost": "à¹à¸žà¹‡à¸„เà¸à¹‡à¸•ที่หายไป", - "app.video.stats.bitrate": "บิตเรท", - "app.video.stats.lostPercentage": "เปà¸à¸£à¹Œà¹€à¸‹à¹‡à¸™à¸•์ที่หายไปทั้งหมด", - "app.video.stats.lostRecentPercentage": "เปà¸à¸£à¹Œà¹€à¸‹à¹‡à¸™à¸•์ล่าสุดที่หายไป", - "app.video.stats.dimensions": "ขนาด", - "app.video.stats.codec": "Codec", - "app.video.stats.decodeDelay": "หน่วงเวลาà¸à¸²à¸£à¸–à¸à¸”รหัส", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "ใช้งานà¸à¸²à¸£à¹€à¸‚้ารหัส", - "app.video.stats.currentDelay": "หน่วงเวลาปัจจุบัน", "app.fullscreenButton.label": "à¸à¸³à¸«à¸™à¸” {0} เต็มหน้าจà¸", "app.meeting.endNotification.ok.label": "ตà¸à¸¥à¸‡", - "app.whiteboard.annotations.poll": "เผยà¹à¸žà¸£à¹ˆà¸œà¸¥à¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸„วามคิดเห็นà¹à¸¥à¹‰à¸§", "app.whiteboard.toolbar.tools": "เครื่à¸à¸‡à¸¡à¸·à¸", "app.whiteboard.toolbar.tools.hand": "à¹à¸–บ", "app.whiteboard.toolbar.tools.pencil": "ดินสà¸", @@ -643,7 +622,6 @@ "app.externalVideo.autoPlayWarning": "เล่นวิดีโà¸à¹€à¸žà¸·à¹ˆà¸à¹€à¸›à¸´à¸”ใช้งานà¸à¸²à¸£à¸‹à¸´à¸‡à¹‚ครไนซ์สื่à¸", "app.network.connection.effective.slow": "เราà¸à¸³à¸¥à¸±à¸‡à¸ªà¸±à¸‡à¹€à¸à¸•เห็นปัà¸à¸«à¸²à¸à¸²à¸£à¹€à¸Šà¸·à¹ˆà¸à¸¡à¸•่à¸", "app.network.connection.effective.slow.help": "ข้à¸à¸¡à¸¹à¸¥à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¸™à¸µà¹‰", - "app.externalVideo.noteLabel": "หมายเหตุ: วิดีโà¸à¸ ายนà¸à¸à¸—ี่à¹à¸Šà¸£à¹Œà¸ˆà¸°à¹„ม่ปราà¸à¸à¹ƒà¸™à¸à¸²à¸£à¸šà¸±à¸™à¸—ึภรà¸à¸‡à¸£à¸±à¸š YouTube, Vimeo, สื่à¸à¸à¸²à¸£à¸ªà¸à¸™, Twitch à¹à¸¥à¸° Daily Motion", "app.actionsBar.actionsDropdown.shareExternalVideo": "à¹à¸šà¹ˆà¸‡à¸›à¸±à¸™à¸§à¸µà¸”ีโà¸à¸ ายนà¸à¸", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "หยุดà¹à¸šà¹ˆà¸‡à¸›à¸±à¸™à¸§à¸µà¸”ีโà¸à¸ ายนà¸à¸", "app.iOSWarning.label": "à¸à¸£à¸¸à¸“าà¸à¸±à¸žà¹€à¸”ทเป็น iOS 12.2 หรืà¸à¸ªà¸¹à¸‡à¸à¸§à¹ˆà¸²", diff --git a/bigbluebutton-html5/private/locales/tr.json b/bigbluebutton-html5/private/locales/tr.json index b8a84389bc0f4fcbf88927c3374be58799a98ced..b680abb3fd7d9af579f974e3b870e12c4dbc3207 100644 --- a/bigbluebutton-html5/private/locales/tr.json +++ b/bigbluebutton-html5/private/locales/tr.json @@ -50,10 +50,10 @@ "app.note.title": "Paylaşılan Notlar", "app.note.label": "Not", "app.note.hideNoteLabel": "Notu gizle", + "app.note.tipLabel": "Düzenleyici araç çubuÄŸuna odaklanmak için Esc tuÅŸuna basın", "app.user.activityCheck": "Kullanıcı etkinliÄŸi denetimi", "app.user.activityCheck.label": "Kullanıcının hala toplantıda olup olmadığını denetleyin ({0})", "app.user.activityCheck.check": "Denetle", - "app.note.tipLabel": "Düzenleyici araç çubuÄŸuna odaklanmak için Esc tuÅŸuna basın", "app.userList.usersTitle": "Kullanıcılar", "app.userList.participantsTitle": "Katılımcılar", "app.userList.messagesTitle": "İletiler", @@ -126,8 +126,10 @@ "app.meeting.meetingTimeRemaining": "Toplantının bitmesine kalan süre: {0}", "app.meeting.meetingTimeHasEnded": "Zaman doldu. Toplantı birazdan bitirilecek", "app.meeting.endedMessage": "Açılış ekranına geri döneceksiniz", - "app.meeting.alertMeetingEndsUnderOneMinute": "Toplantı bir dakika içinde bitirilecek.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Grup çalışması bir dakika içinde sone erecek.", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "Toplantı bir dakika içinde bitirilecek.", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "Toplantı {0} dakika içinde bitirilecek.", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "Grup çalışması {0} dakika içinde bitirilecek.", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "Grup çalışması bir dakika içinde sona erecek.", "app.presentation.hide": "Sunumu gizle", "app.presentation.notificationLabel": "Geçerli sunum", "app.presentation.slideContent": "Slayt İçeriÄŸi", @@ -267,7 +269,7 @@ "app.leaveConfirmation.confirmLabel": "Ayrıl", "app.leaveConfirmation.confirmDesc": "Sizi görüşmeden çıkarır", "app.endMeeting.title": "Toplantıyı bitir", - "app.endMeeting.description": "Bu oturumu sonlandırmak istediÄŸinize emin misiniz?", + "app.endMeeting.description": "Bu oturumu herkes için sonlandırmak istediÄŸinize emin misiniz (tüm kullanıcıların baÄŸlantısı kesilecek)?", "app.endMeeting.yesLabel": "Evet", "app.endMeeting.noLabel": "Hayır", "app.about.title": "Hakkında", @@ -288,10 +290,6 @@ "app.screenshare.screenShareLabel" : "Ekran paylaşımı", "app.submenu.application.applicationSectionTitle": "Uygulama", "app.submenu.application.animationsLabel": "Canlandırmalar", - "app.submenu.application.audioAlertLabel": "Sesli Sohbet Uyarıları", - "app.submenu.application.pushAlertLabel": "Açılır Pencere Sohbet Uyarıları", - "app.submenu.application.userJoinAudioAlertLabel": "Sesli Kullanıcı Katılımı Uyarıları", - "app.submenu.application.userJoinPushAlertLabel": "Açılır Pencere Kullanıcı Katılımı Uyarıları", "app.submenu.application.fontSizeControlLabel": "Yazı boyutu", "app.submenu.application.increaseFontBtnLabel": "Uygulamanın yazı boyutunu büyüt", "app.submenu.application.decreaseFontBtnLabel": "Uygulamanın yazı boyutunu küçült", @@ -299,6 +297,7 @@ "app.submenu.application.languageLabel": "Uygulama Dili", "app.submenu.application.languageOptionLabel": "Dil seçin", "app.submenu.application.noLocaleOptionLabel": "Etkin bir dil bulunamadı", + "app.submenu.notification.SectionTitle": "Bildirimler", "app.submenu.audio.micSourceLabel": "Mikrofon kaynağı", "app.submenu.audio.speakerSourceLabel": "Hoparlör kaynağı", "app.submenu.audio.streamVolumeLabel": "Sesinizin düzeyi", @@ -322,6 +321,7 @@ "app.settings.dataSavingTab.screenShare": "Masaüstü paylaşılabilsin", "app.settings.dataSavingTab.description": "Bant geniÅŸliÄŸinden tasarruf etmek için nelerin görüntüleneceÄŸini ayarlayın.", "app.settings.save-notification.label": "Ayarlar kaydedildi", + "app.statusNotifier.and": "ve", "app.switch.onLabel": "AÇIK", "app.switch.offLabel": "KAPALI", "app.talkingIndicator.ariaMuteDesc" : "Sesini kapatacağınız kullanıcıyı seçin", @@ -414,7 +414,7 @@ "app.audioModal.helpTitle": "Ortam aygıtlarınızla ilgili bir sorun çıktı", "app.audioModal.helpText": "Mikrofonunuza eriÅŸme izni verdiniz mi? Görüşmeye sesinizle katılmak istediÄŸinizde, ortam aygıtlarınıza eriÅŸme izni vermeniz için bir pencere görüntülenir. Sesli görüşmeye katılabilmek için onay vermeniz gerekir. İzin penceresi görüntülenmediyse, web tarayıcınızın ayarlarından mikrofon izinlerini deÄŸiÅŸtirmeyi deneyin.", "app.audioModal.help.noSSL": "Bu sayfa güvenli baÄŸlantı kullanmıyor. Mikrofon eriÅŸimine izin verilebilmesi için sayfa baÄŸlantısı HTTPS . ile kurulmalıdır. Lütfen sunucu yöneticisi ile görüşün.", - "app.audioModal.help.macNotAllowed": " Mac Sistem Tercihleriniz mikrofonunuza eriÅŸimi engelliyor gibi görünüyor. Sistem Tercihleri > ​​Güvenlik ve Gizlilik > Gizlilik > Mikrofon açın ve kullandığınız tarayıcının kontrol edildiÄŸini doÄŸrulayın.", + "app.audioModal.help.macNotAllowed": "Mac Sistem Tercihleriniz mikrofonunuza eriÅŸimi engelliyor gibi görünüyor. Sistem Tercihleri > ​​Güvenlik ve Gizlilik > Gizlilik > Mikrofon açın ve kullandığınız tarayıcının kontrol edildiÄŸini doÄŸrulayın.", "app.audioModal.audioDialTitle": "Telefonunuz ile katılın", "app.audioDial.audioDialDescription": "Ara", "app.audioDial.audioDialConfrenceText": "ve görüşmeye katılmak için PIN numarasını yazın:", @@ -432,7 +432,7 @@ "app.audioManager.genericError": "Hata: Bir sorun çıktı. Lütfen yeniden deneyin", "app.audioManager.connectionError": "Hata: BaÄŸlantı sorunu", "app.audioManager.requestTimeout": "Hata: İstek zaman aşımına uÄŸradı", - "app.audioManager.invalidTarget": "Hata: Geçersiz hedeften talep denemesi hatası", + "app.audioManager.invalidTarget": "Hata: Geçersiz bir hedef için bir istekte bulunuldu", "app.audioManager.mediaError": "Hata: Ortam aygıtlarınıza eriÅŸilirken bir sorun çıktı", "app.audio.joinAudio": "Sesli katıl", "app.audio.leaveAudio": "Sesli Katılımı Kapat", @@ -454,7 +454,7 @@ "app.meeting.logout.duplicateUserEjectReason": "Aynı kullanıcı toplantıya ikinci kez katılmaya çalışıyor", "app.meeting.logout.permissionEjectReason": "İzin ihlali nedeniyle çıkarıldı", "app.meeting.logout.ejectedFromMeeting": "Toplantıdan çıkarıldınız", - "app.meeting.logout.validateTokenFailedEjectReason": "Yetkilendirme belirteci/token doÄŸrulanamadı", + "app.meeting.logout.validateTokenFailedEjectReason": "Kimlik doÄŸrulama kodu doÄŸrulanamadı", "app.meeting.logout.userInactivityEjectReason": "Kullanıcı uzun süredir etkin deÄŸil", "app.meeting-ended.rating.legendLabel": "Geri bildirim deÄŸerlendirmesi", "app.meeting-ended.rating.starLabel": "Yıldız", @@ -500,6 +500,7 @@ "app.notification.recordingPaused": "Bu oturum artık kaydedilmiyor", "app.notification.recordingAriaLabel": "Kaydedilen süre", "app.notification.userJoinPushAlert": "{0} oturuma katıldı", + "app.submenu.notification.raiseHandLabel": "El kaldır", "app.shortcut-help.title": "TuÅŸ takımı kısayolları", "app.shortcut-help.accessKeyNotAvailable": "EriÅŸim tuÅŸları kullanılamıyor", "app.shortcut-help.comboLabel": "Açılan Kutu", @@ -540,10 +541,15 @@ "app.recording.stopDescription": "Kaydı duraklatmak istediÄŸinize emin misiniz? Kayıt düğmesine yeniden basarak kaydı sürdürebilirsiniz.", "app.videoPreview.cameraLabel": "Kamera", "app.videoPreview.profileLabel": "Kalite", + "app.videoPreview.quality.low": "Düşük", + "app.videoPreview.quality.medium": "Orta", + "app.videoPreview.quality.high": "Yüksek", + "app.videoPreview.quality.hd": "Yüksek çözünürlük", "app.videoPreview.cancelLabel": "Vazgeç", "app.videoPreview.closeLabel": "Kapat", "app.videoPreview.findingWebcamsLabel": "Kameralar bulunuyor", "app.videoPreview.startSharingLabel": "Paylaşımı baÅŸlat", + "app.videoPreview.stopSharingLabel": "Paylaşımı durdur", "app.videoPreview.webcamOptionLabel": "Kamera seçin", "app.videoPreview.webcamPreviewLabel": "Kamera ön izlemesi", "app.videoPreview.webcamSettingsTitle": "Kamera ayarları", @@ -573,32 +579,18 @@ "app.video.videoMenuDesc": "Görüntü menüsü listesini aç", "app.video.chromeExtensionError": "Åžunu kurmalısınız:", "app.video.chromeExtensionErrorLink": "Chrome eklentisi", - "app.video.stats.title": "BaÄŸlantı İstatistikleri", - "app.video.stats.packetsReceived": "Gelen paket", - "app.video.stats.packetsSent": "Giden paket", - "app.video.stats.packetsLost": "Kayıp paket", - "app.video.stats.bitrate": "Bit hızı", - "app.video.stats.lostPercentage": "Toplam kayıp yüzdesi", - "app.video.stats.lostRecentPercentage": "Son kayıp yüzdesi", - "app.video.stats.dimensions": "Boyutlar", - "app.video.stats.codec": "Kodlayıcı/çözücü", - "app.video.stats.decodeDelay": "Kod çözme gecikmesi", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Kodlama kullanımı", - "app.video.stats.currentDelay": "Åžu andaki gecikme", "app.fullscreenButton.label": "{0} tam ekran yap", - "app.deskshare.iceConnectionStateError": "Ekran paylaşımı sırasında baÄŸlantı hatası (ICE hatası 1108)", - "app.sfu.mediaServerConnectionError2000": "Medya sunucusuna ulaşılamıyor (hata 2000)", - "app.sfu.mediaServerOffline2001": "Medya sunucusu çevrimdışı. Lütfen daha sonra tekrar deneyin (hata 2001)", - "app.sfu.mediaServerNoResources2002": "Medya sunucunun kullanım için uygun kaynağı yok (hata 2002)", - "app.sfu.mediaServerRequestTimeout2003": "Medya sunucu istekleri zaman aşımına uÄŸruyor (hata 2003)", - "app.sfu.serverIceGatheringFailed2021": "Medya sunucu baÄŸlantı parametrelerini alamıyor (ICE hatası 2021)", - "app.sfu.serverIceGatheringFailed2022": "Medya sunucu baÄŸlantı hatası (ICE hatası 2022)", - "app.sfu.mediaGenericError2200": "Medya sunucusu isteÄŸi iÅŸleyemiyor (ICE hatası 2200)", - "app.sfu.invalidSdp2202":"İstemci geçersiz medya isteÄŸi talebi oluÅŸturdu (SDP hatası 2202)", - "app.sfu.noAvailableCodec2203": "Sunucu uygun medya kodlaması bulamadı (hata 2203)", + "app.deskshare.iceConnectionStateError": "Ekran paylaşılırken baÄŸlantı kesildi (ICE hatası 1108)", + "app.sfu.mediaServerConnectionError2000": "Ortam sunucusu ile baÄŸlantı kurulamadı (hata 2000)", + "app.sfu.mediaServerOffline2001": "Ortam sunucusu çevrimdışı. Lütfen daha sonra yeniden deneyin (hata 2001)", + "app.sfu.mediaServerNoResources2002": "Ortam sunucunda kullanılabilecek kaynak yok (hata 2002)", + "app.sfu.mediaServerRequestTimeout2003": "Ortam sunucu istekleri zaman aşımına uÄŸradı (hata 2003)", + "app.sfu.serverIceGatheringFailed2021": "Ortam sunucusu baÄŸlantı adaylarını alamadı (ICE hatası 2021)", + "app.sfu.serverIceGatheringFailed2022": "Ortam sunucusu baÄŸlantısı kurulamadı (ICE hatası 2022)", + "app.sfu.mediaGenericError2200": "Ortam sunucusu isteÄŸi iÅŸleyemedi (ICE hatası 2200)", + "app.sfu.invalidSdp2202":"İstemci geçersiz bir ortam isteÄŸinde bulundu (SDP hatası 2202)", + "app.sfu.noAvailableCodec2203": "Sunucu uygun bir ortam kodlayıcı/çözücüsü bulamadı (hata 2203)", "app.meeting.endNotification.ok.label": "Tamam", - "app.whiteboard.annotations.poll": "Oylama sonuçları yayınlandı", "app.whiteboard.toolbar.tools": "Araçlar", "app.whiteboard.toolbar.tools.hand": "Sunum araçları", "app.whiteboard.toolbar.tools.pencil": "Kalem", @@ -627,9 +619,9 @@ "app.whiteboard.toolbar.clear": "Tüm Ek Açıklamaları Temizle", "app.whiteboard.toolbar.multiUserOn": "Çoklu kullanıcı modunu aç", "app.whiteboard.toolbar.multiUserOff": "Çoklu kullanıcı modunu kapat", - "app.whiteboard.toolbar.fontSize": "Yazı tipi boyutu listesi", + "app.whiteboard.toolbar.fontSize": "Yazı tipi listesi", "app.feedback.title": "Görüşmeden çıktınız", - "app.feedback.subtitle": "BigBlueButton deneyiminizi bizimle paylaşın (zorunlu deÄŸil)", + "app.feedback.subtitle": "BigBlueButton deneyiminizi bizimle paylaşın (isteÄŸe baÄŸlı)", "app.feedback.textarea": "BigBlueButton'ı nasıl daha iyi yapabiliriz?", "app.feedback.sendFeedback": "Geri Bildirim Gönder", "app.feedback.sendFeedbackDesc": "Bir geri bildirim gönderip toplantıdan çıkın", @@ -679,7 +671,6 @@ "app.externalVideo.autoPlayWarning": "Medya eÅŸleÅŸtirmesini etkinleÅŸtirmek için videoyu oynatın", "app.network.connection.effective.slow": "BaÄŸlantı sorunlarını tespit ediyoruz", "app.network.connection.effective.slow.help": "Daha fazla bilgi", - "app.externalVideo.noteLabel": "Not: Paylaşılan harici videolar kayıtta görünmez. YouTube, Vimeo, Instructure Media, Twitch ve Daily Motion URL'leri desteklenir.", "app.actionsBar.actionsDropdown.shareExternalVideo": "Dışarıdan bir görüntü paylaşın", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Dış görüntü paylaşımını durdur", "app.iOSWarning.label": "Lütfen iOS 12.2 ya da üzerindeki bir sürüme yükseltin", diff --git a/bigbluebutton-html5/private/locales/tr_TR.json b/bigbluebutton-html5/private/locales/tr_TR.json index 52f983f46cdc290dacf356d4893eb24c572b7e54..8c12540bc12f03d9dee8a130764d095a214d3936 100644 --- a/bigbluebutton-html5/private/locales/tr_TR.json +++ b/bigbluebutton-html5/private/locales/tr_TR.json @@ -50,10 +50,10 @@ "app.note.title": "Paylaşılan Notlar", "app.note.label": "Not", "app.note.hideNoteLabel": "Notu gizle", + "app.note.tipLabel": "Editör araç çubuÄŸuna odaklanmak için Esc tuÅŸuna basın", "app.user.activityCheck": "Kullanıcı etkinliÄŸi kontrolü", "app.user.activityCheck.label": "Kullanıcının hala toplantıda olup olmadığını kontrol edin ({0})", "app.user.activityCheck.check": "Kontrol et", - "app.note.tipLabel": "Editör araç çubuÄŸuna odaklanmak için Esc tuÅŸuna basın", "app.userList.usersTitle": "Kullanıcılar", "app.userList.participantsTitle": "Katılımcılar", "app.userList.messagesTitle": "Mesajlar", @@ -126,8 +126,6 @@ "app.meeting.meetingTimeRemaining": "Oturumun bitmesine kalan süre: {0}", "app.meeting.meetingTimeHasEnded": "Zaman bitti. Oturum kısa süre sonra kapanacak", "app.meeting.endedMessage": "Ana ekrana geri yönlendirileceksiniz", - "app.meeting.alertMeetingEndsUnderOneMinute": "Oturum bir dakika içinde kapanacak.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Çalışma odası bir dakika içinde kapanıyor.", "app.presentation.hide": "Sunumu gizle", "app.presentation.notificationLabel": "Geçerli sunum", "app.presentation.slideContent": "Slayt içeriÄŸi", @@ -267,7 +265,6 @@ "app.leaveConfirmation.confirmLabel": "Ayrıl", "app.leaveConfirmation.confirmDesc": "Sizi görüşmeden çıkarır", "app.endMeeting.title": "Oturumu sonlandır", - "app.endMeeting.description": "Bu oturumu sonlandırmak istediÄŸinize emin misiniz?", "app.endMeeting.yesLabel": "Evet", "app.endMeeting.noLabel": "Hayır", "app.about.title": "Hakkında", @@ -288,10 +285,6 @@ "app.screenshare.screenShareLabel" : "Ekran paylaşımı", "app.submenu.application.applicationSectionTitle": "Uygulama", "app.submenu.application.animationsLabel": "Animasyonlar", - "app.submenu.application.audioAlertLabel": "Sohbet Sesli Uyarıları", - "app.submenu.application.pushAlertLabel": "Sohbet Açılır Pencere Uyarıları", - "app.submenu.application.userJoinAudioAlertLabel": "Kullanıcı katılımı için sesli uyarılar", - "app.submenu.application.userJoinPushAlertLabel": "Kullanıcı katılımı için açılır pencere uyarıları", "app.submenu.application.fontSizeControlLabel": "Yazı büyüklüğü", "app.submenu.application.increaseFontBtnLabel": "Uygulama Yazı Büyüklüğünü Artır", "app.submenu.application.decreaseFontBtnLabel": "Uygulama Yazı Büyüklüğünü Azalt", @@ -573,19 +566,6 @@ "app.video.videoMenuDesc": "Video menüsünü liste olarak aç", "app.video.chromeExtensionError": "Yüklemeniz gerekiyor", "app.video.chromeExtensionErrorLink": "bu Chrome uzantısı", - "app.video.stats.title": "BaÄŸlantı İstatistikleri", - "app.video.stats.packetsReceived": "Gelen paketler", - "app.video.stats.packetsSent": "Giden paketler", - "app.video.stats.packetsLost": "Kayıp paketler", - "app.video.stats.bitrate": "Bit hızı", - "app.video.stats.lostPercentage": "Toplam kayıp yüzdesi", - "app.video.stats.lostRecentPercentage": "Son kayıp yüzdesi", - "app.video.stats.dimensions": "Boyutlar", - "app.video.stats.codec": "Kodek", - "app.video.stats.decodeDelay": "Kod çözme gecikmesi", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Çözümleme kullanımı", - "app.video.stats.currentDelay": "Mevcut gecikme", "app.fullscreenButton.label": "{0} tam ekran yap", "app.deskshare.iceConnectionStateError": "Ekran paylaşımı sırasında baÄŸlantı hatası (ICE hatası 1108)", "app.sfu.mediaServerConnectionError2000": "Medya sunucusuna ulaşılamıyor (hata 2000)", @@ -598,7 +578,6 @@ "app.sfu.invalidSdp2202":"İstemci geçersiz medya isteÄŸi talebi oluÅŸturdu (SDP hatası 2202)", "app.sfu.noAvailableCodec2203": "Sunucu uygun medya kodlaması bulamadı (hata 2203)", "app.meeting.endNotification.ok.label": "TAMAM", - "app.whiteboard.annotations.poll": "Anket sonuçları yayınlandı", "app.whiteboard.toolbar.tools": "Araçlar", "app.whiteboard.toolbar.tools.hand": "Sunum araçları", "app.whiteboard.toolbar.tools.pencil": "Kalem", @@ -679,7 +658,6 @@ "app.externalVideo.autoPlayWarning": "Medya senkronizasyonunu etkinleÅŸtirmek için videoyu oynatın", "app.network.connection.effective.slow": "BaÄŸlantı sorunlarını tespit ediyoruz", "app.network.connection.effective.slow.help": "Daha fazla bilgi", - "app.externalVideo.noteLabel": "Not: Paylaşılan harici videolar kayıtta görünmez. YouTube, Vimeo, Instructure Media, Twitch ve Daily Motion URL'leri desteklenir.", "app.actionsBar.actionsDropdown.shareExternalVideo": "Harici bir video paylaşın", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Harici video paylaşımını durdur", "app.iOSWarning.label": "Lütfen iOS 12.2 veya daha üstüne yükseltin", diff --git a/bigbluebutton-html5/private/locales/uk_UA.json b/bigbluebutton-html5/private/locales/uk_UA.json index dfc7d2664f631ddb1725ab0cbba18462f402b344..7d4c58ba455bf4a92d35f366d883febbe227f347 100644 --- a/bigbluebutton-html5/private/locales/uk_UA.json +++ b/bigbluebutton-html5/private/locales/uk_UA.json @@ -18,6 +18,7 @@ "app.chat.dropdown.save": "Зберегти", "app.chat.label": "Чат", "app.chat.offline": "Ðе в мережі", + "app.chat.pollResult": "Результати опитуваннÑ", "app.chat.emptyLogLabel": "Журнал чату порожній", "app.chat.clearPublicChatMessage": "ІÑторію загального чату очищено ведучим", "app.chat.multi.typing": "УчаÑники пишуть", @@ -50,15 +51,15 @@ "app.note.title": "Спільні нотатки", "app.note.label": "Ðотатки", "app.note.hideNoteLabel": "Сховати нотатки", + "app.note.tipLabel": "ÐатиÑніть Esc, щоб ÑфокуÑувати панель інÑтрументів редактора", "app.user.activityCheck": "Перевірка активноÑті учаÑника", "app.user.activityCheck.label": "Перевірте, чи учаÑник зараз на зуÑтрiчi ({0})", "app.user.activityCheck.check": "Перевірка", - "app.note.tipLabel": "ÐатиÑніть Esc, щоб ÑфокуÑувати панель інÑтрументів редактора", "app.userList.usersTitle": "УчаÑники", "app.userList.participantsTitle": "УчаÑники", "app.userList.messagesTitle": "ПовідомленнÑ", "app.userList.notesTitle": "Ðотатки", - "app.userList.notesListItem.unreadContent": "ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñƒ розділі \"Спільні нотатки\"", + "app.userList.notesListItem.unreadContent": "ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñƒ розділі \\\"Спільні нотатки\\\"", "app.userList.captionsTitle": "Субтитри", "app.userList.presenter": "Ведучий", "app.userList.you": "Ви", @@ -95,6 +96,8 @@ "app.userList.userOptions.unmuteAllDesc": "СкаÑовує Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð½Ñ Ð¼Ñ–ÐºÑ€Ð¾Ñ„Ð¾Ð½Ñƒ", "app.userList.userOptions.lockViewersLabel": "ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ñ„ÑƒÐ½ÐºÑ†Ñ–Ð¹ учаÑникам", "app.userList.userOptions.lockViewersDesc": "Обмежити вебкамеру, мікрофон, можливіÑть пиÑати в чат...", + "app.userList.userOptions.connectionStatusLabel": "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð·'єднаннÑ", + "app.userList.userOptions.connectionStatusDesc": "ПереглÑд Ñтану з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувачів", "app.userList.userOptions.disableCam": "Вебкамери учаÑників вимкнено", "app.userList.userOptions.disableMic": "Мікрофони учаÑників вимкнено", "app.userList.userOptions.disablePrivChat": "Приватний чат вимкнено", @@ -125,10 +128,12 @@ "app.meeting.ended": "Цей ÑÐµÐ°Ð½Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐ¸Ð²ÑÑ", "app.meeting.meetingTimeRemaining": "ЗалишилоÑÑŒ чаÑу зуÑтрічі: {0}", "app.meeting.meetingTimeHasEnded": "Ð§Ð°Ñ Ð·Ð°ÐºÑ–Ð½Ñ‡Ð¸Ð²ÑÑ. ЗуÑтріч буде закрито незабаром", - "app.meeting.endedMessage": "ПереÑпрÑÐ¼ÑƒÐ²Ð°Ð½Ð½Ñ Ð½Ð° головний екран", - "app.meeting.alertMeetingEndsUnderOneMinute": "ЗуÑтріч закінчуєтьÑÑ Ñ‡ÐµÑ€ÐµÐ· хвилину.", - "app.meeting.alertBreakoutEndsUnderOneMinute": "ЗуÑтріч закінчуєтьÑÑ Ñ‡ÐµÑ€ÐµÐ· хвилину.", - "app.presentation.hide": "Згорнути презентацію", + "app.meeting.endedMessage": "Ð’Ð°Ñ Ð±ÑƒÐ´Ðµ перенаправлено на головну Ñторінку", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "ЗуÑтріч завершуєтьÑÑ Ð·Ð° 1 хв.", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "ЗуÑтріч завершуєтьÑÑ Ð·Ð° {0} хв.", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "Перерва закінчитьÑÑ Ð·Ð° {0} хв.", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "Перерва закінчитьÑÑ Ð·Ð° 1 хв.", + "app.presentation.hide": "Приховати презентацію", "app.presentation.notificationLabel": "Поточна презентаціÑ", "app.presentation.slideContent": "ВміÑÑ‚ Ñлайду", "app.presentation.startSlideContent": "Початок вміÑту Ñлайду", @@ -143,25 +148,25 @@ "app.presentation.presentationToolbar.nextSlideDesc": "Перемкнути презентацію на наÑтупний Ñлайд", "app.presentation.presentationToolbar.skipSlideLabel": "ПропуÑтити Ñлайд", "app.presentation.presentationToolbar.skipSlideDesc": "Перемкнути презентацію на вказаний Ñлайд", - "app.presentation.presentationToolbar.fitWidthLabel": "По ширині", + "app.presentation.presentationToolbar.fitWidthLabel": "УміÑтити за шириною", "app.presentation.presentationToolbar.fitWidthDesc": "Показати вÑÑŽ ширину Ñлайда", - "app.presentation.presentationToolbar.fitScreenLabel": "За розміром екрану", + "app.presentation.presentationToolbar.fitScreenLabel": "УміÑтити на екран", "app.presentation.presentationToolbar.fitScreenDesc": "Показати веÑÑŒ Ñлайд", - "app.presentation.presentationToolbar.zoomLabel": "МаÑштаб", - "app.presentation.presentationToolbar.zoomDesc": "Змінити маÑштаб презентації", + "app.presentation.presentationToolbar.zoomLabel": "ЗбільшеннÑ", + "app.presentation.presentationToolbar.zoomDesc": "Змінити рівень Ð·Ð±Ñ–Ð»ÑŒÑˆÐµÐ½Ð½Ñ Ð¿Ñ€ÐµÐ·ÐµÐ½Ñ‚Ð°Ñ†Ñ–Ñ—", "app.presentation.presentationToolbar.zoomInLabel": "Збільшити", "app.presentation.presentationToolbar.zoomInDesc": "Збільшити презентацію", "app.presentation.presentationToolbar.zoomOutLabel": "Зменшити", "app.presentation.presentationToolbar.zoomOutDesc": "Зменшити презентацію", - "app.presentation.presentationToolbar.zoomReset": "Типово", - "app.presentation.presentationToolbar.zoomIndicator": "Поточне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð¼Ð°Ñштабу", - "app.presentation.presentationToolbar.fitToWidth": "По ширині", - "app.presentation.presentationToolbar.fitToPage": "За розміром Ñторінки", + "app.presentation.presentationToolbar.zoomReset": "Скинути збільшеннÑ", + "app.presentation.presentationToolbar.zoomIndicator": "Поточне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð·Ð±Ñ–Ð»ÑŒÑˆÐµÐ½Ð½Ñ", + "app.presentation.presentationToolbar.fitToWidth": "УміÑтити за шириною", + "app.presentation.presentationToolbar.fitToPage": "УміÑтити на Ñторінку", "app.presentation.presentationToolbar.goToSlide": "Слайд {0}", "app.presentationUploder.title": "ПрезентаціÑ", "app.presentationUploder.message": "Ведучий може завантажувати будь-Ñкий документ офіÑного формату, включно PDF. Ми рекомендуємо завантажувати презентації Ñаме у форматі PDF. ПіÑÐ»Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð¿Ð¾Ñтавте прапорець навпроти імені файлу, Ñкий ви хочете показати учаÑникам.", "app.presentationUploder.uploadLabel": "Завантажити", - "app.presentationUploder.confirmLabel": "Гаразд", + "app.presentationUploder.confirmLabel": "Підтвердити", "app.presentationUploder.confirmDesc": "Зберегти зміни та розпочати презентацію", "app.presentationUploder.dismissLabel": "СкаÑувати", "app.presentationUploder.dismissDesc": "Закрити вікно Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ñ‚Ð° ÑкаÑувати ваші зміни", @@ -181,7 +186,7 @@ "app.presentationUploder.conversion.genericConversionStatus": "Файл конвертуєтьÑÑ...", "app.presentationUploder.conversion.generatingThumbnail": "Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¼Ñ–Ð½Ñ–Ð°Ñ‚ÑŽÑ€...", "app.presentationUploder.conversion.generatedSlides": "Слайди ÑтворюютьÑÑ...", - "app.presentationUploder.conversion.generatingSvg": "Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñлайдів SVG...", + "app.presentationUploder.conversion.generatingSvg": "Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½ÑŒ SVG...", "app.presentationUploder.conversion.pageCountExceeded": "Перевищено кількіÑть Ñторінок. Будь лаÑка, розділіть файл на декілька.", "app.presentationUploder.conversion.officeDocConversionInvalid": "Ðе вийшло опрацювати документ. Будь лаÑка, завантажте файл у форматі PDF.", "app.presentationUploder.conversion.officeDocConversionFailed": "Ðе вийшло опрацювати документ. Будь лаÑка, завантажте файл у форматі PDF.", @@ -202,7 +207,7 @@ "app.poll.quickPollInstruction": "Виберіть типовий шаблон опитуваннÑ.", "app.poll.customPollLabel": "ВлаÑне опитуваннÑ", "app.poll.startCustomLabel": "Розпочати влаÑне опитуваннÑ", - "app.poll.activePollInstruction": "Залиште цю панель відкритою, щоб бачити відповіді на Ð¾Ð¿Ð¸Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð² реальному чаÑÑ–. Коли будете готові, оберіть \"Опублікувати результати опитуваннÑ\", щоб зробити результати доÑтупними учаÑникам Ñ– таким чином завершити опитуваннÑ.", + "app.poll.activePollInstruction": "Залиште цю панель відкритою, щоб бачити відповіді на Ð¾Ð¿Ð¸Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð² реальному чаÑÑ–. Коли будете готові, оберіть \\\"Опублікувати результати опитуваннÑ\\\", щоб зробити результати доÑтупними учаÑникам Ñ– таким чином завершити опитуваннÑ.", "app.poll.publishLabel": "Опублікувати результати опитуваннÑ", "app.poll.backLabel": "Ðазад до параметрів опитуваннÑ", "app.poll.closeLabel": "Закрити", @@ -211,9 +216,9 @@ "app.poll.customPlaceholder": "Додати варіант опитуваннÑ", "app.poll.noPresentationSelected": "Ðе вибрано жодної презентації! Виберіть щонайменше одну.", "app.poll.clickHereToSelect": "Клацніть тут Ð´Ð»Ñ Ð²Ð¸Ð±Ð¾Ñ€Ñƒ", - "app.poll.t": "Правильно", + "app.poll.t": "ДійÑно", "app.poll.f": "Хибно", - "app.poll.tf": "Правильно / хибно", + "app.poll.tf": "ДійÑно / хибно", "app.poll.y": "Так", "app.poll.n": "ÐÑ–", "app.poll.yn": "Так / ÐÑ–", @@ -221,7 +226,7 @@ "app.poll.a3": "A / B / C", "app.poll.a4": "A / B / C / D", "app.poll.a5": "A / B / C / D / E", - "app.poll.answer.true": "Правильно", + "app.poll.answer.true": "ДійÑно", "app.poll.answer.false": "Хибно", "app.poll.answer.yes": "Так", "app.poll.answer.no": "ÐÑ–", @@ -232,11 +237,11 @@ "app.poll.answer.e": "Ò", "app.poll.liveResult.usersTitle": "УчаÑники", "app.poll.liveResult.responsesTitle": "Відповідь", - "app.polling.pollingTitle": "ОпитуваннÑ: оберіть варіант", - "app.polling.pollAnswerLabel": "Результат Ð¾Ð¿Ð¸Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ {0}", + "app.polling.pollingTitle": "Варіанти опитуваннÑ", + "app.polling.pollAnswerLabel": "Відповідь Ð¾Ð¿Ð¸Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ {0}", "app.polling.pollAnswerDesc": "Виберіть цей варіант щоб проголоÑувати за {0}", "app.failedMessage": "Вибачте, проблеми з підключеннÑм до Ñервера.", - "app.downloadPresentationButton.label": "Звантажити документ", + "app.downloadPresentationButton.label": "Звантажити оригінал презентації", "app.connectingMessage": "З'єднаннÑ...", "app.waitingMessage": "Втрачено з'єднаннÑ. Спроба повторного з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ñ‡ÐµÑ€ÐµÐ· {0} Ñекунд...", "app.retryNow": "Повторити", @@ -244,18 +249,18 @@ "app.navBar.settingsDropdown.fullscreenLabel": "Ðа веÑÑŒ екран", "app.navBar.settingsDropdown.settingsLabel": "ÐалаштуваннÑ", "app.navBar.settingsDropdown.aboutLabel": "Про заÑтоÑунок", - "app.navBar.settingsDropdown.leaveSessionLabel": "Вийти", + "app.navBar.settingsDropdown.leaveSessionLabel": "ВилогінитиÑÑ", "app.navBar.settingsDropdown.exitFullscreenLabel": "Вийти з повноекранного режиму", "app.navBar.settingsDropdown.fullscreenDesc": "Розгорнути меню параметрів на веÑÑŒ екран", "app.navBar.settingsDropdown.settingsDesc": "Змінити загальні налаштуваннÑ", "app.navBar.settingsDropdown.aboutDesc": "Показати інформацію про клієнта", - "app.navBar.settingsDropdown.leaveSessionDesc": "Вийти", + "app.navBar.settingsDropdown.leaveSessionDesc": "Вийти із зуÑтрічі", "app.navBar.settingsDropdown.exitFullscreenDesc": "Вийти з повноекранного режиму", "app.navBar.settingsDropdown.hotkeysLabel": "ГарÑчі клавіші", "app.navBar.settingsDropdown.hotkeysDesc": "Перелік гарÑчих клавiш", "app.navBar.settingsDropdown.helpLabel": "Допомога", "app.navBar.settingsDropdown.helpDesc": "ПереÑпрÑмовує учаÑника до відео з інÑтрукціÑми (нова вкладка)", - "app.navBar.settingsDropdown.endMeetingDesc": "Завершити зуÑтріч", + "app.navBar.settingsDropdown.endMeetingDesc": "Завершує поточну зуÑтріч", "app.navBar.settingsDropdown.endMeetingLabel": "Завершити зуÑтріч", "app.navBar.userListToggleBtnLabel": "Перемкнути ÑпиÑок учаÑників", "app.navBar.toggleUserList.ariaLabel": "Перемкнути учаÑників та повідомленнÑ", @@ -265,16 +270,16 @@ "app.navBar.recording.off": "Ðе запиÑуєтьÑÑ", "app.navBar.emptyAudioBrdige": "Ðемає активного мікрофону. Ðктивуйте Ваш мікрофон, щоб додати звук в даний запиÑ.", "app.leaveConfirmation.confirmLabel": "Вийти", - "app.leaveConfirmation.confirmDesc": "Вийти з конференції", + "app.leaveConfirmation.confirmDesc": "Виводить Ð²Ð°Ñ Ñ–Ð· зуÑтрічі", "app.endMeeting.title": "Завершити зуÑтріч", - "app.endMeeting.description": "Ви точно хочете завершити цю зуÑтріч?", + "app.endMeeting.description": "ДійÑно завершити цю зуÑтріч? ( УÑÑ–Ñ… кориÑтувачів буде від'єднано)", "app.endMeeting.yesLabel": "Так", "app.endMeeting.noLabel": "ÐÑ–", "app.about.title": "Про заÑтоÑунок", "app.about.version": "Збірка клієнта:", "app.about.copyright": "ÐвторÑьке право:", - "app.about.confirmLabel": "Гаразд", - "app.about.confirmDesc": "Гаразд", + "app.about.confirmLabel": "ОК", + "app.about.confirmDesc": "ОК", "app.about.dismissLabel": "СкаÑувати", "app.about.dismissDesc": "Закрити інформацію про клієнта", "app.actionsBar.changeStatusLabel": "Змінити ÑтатуÑ", @@ -283,15 +288,11 @@ "app.actionsBar.camOffLabel": "Вимкнути камеру", "app.actionsBar.raiseLabel": "ПіднÑти", "app.actionsBar.label": "Панель дій", - "app.actionsBar.actionsDropdown.restorePresentationLabel": "Розгорнути презентацію", + "app.actionsBar.actionsDropdown.restorePresentationLabel": "Відновити презентацію", "app.actionsBar.actionsDropdown.restorePresentationDesc": "Кнопка Ð´Ð»Ñ Ð¿Ð¾Ð²ÐµÑ€Ð½ÐµÐ½Ð½Ñ Ð¿Ñ€ÐµÐ·ÐµÐ½Ñ‚Ð°Ñ†Ñ–Ñ—, Ñку було закрито", "app.screenshare.screenShareLabel" : "ДемонÑÑ‚Ñ€Ð°Ñ†Ñ–Ñ ÐµÐºÑ€Ð°Ð½Ñƒ", "app.submenu.application.applicationSectionTitle": "ЗаÑтоÑунок", - "app.submenu.application.animationsLabel": "Ефекти", - "app.submenu.application.audioAlertLabel": "Звукове ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð² чаті", - "app.submenu.application.pushAlertLabel": "Виринаючі ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ñ‡Ð°Ñ‚Ñƒ", - "app.submenu.application.userJoinAudioAlertLabel": "Звукове ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ Ð¿Ñ€Ð¸Ñ”Ð´Ð½Ð°Ð½Ð½Ñ ÑƒÑ‡Ð°Ñника", - "app.submenu.application.userJoinPushAlertLabel": "Виринаючі ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ Ð¿Ñ€Ð¸Ñ”Ð´Ð½Ð°Ð½Ð½Ñ ÑƒÑ‡Ð°Ñника", + "app.submenu.application.animationsLabel": "Ðнімації", "app.submenu.application.fontSizeControlLabel": "Розмір шрифту", "app.submenu.application.increaseFontBtnLabel": "Збільшити розмір шрифту", "app.submenu.application.decreaseFontBtnLabel": "Зменшити розмір шрифту", @@ -299,9 +300,15 @@ "app.submenu.application.languageLabel": "Мова заÑтоÑунку", "app.submenu.application.languageOptionLabel": "Вибрати мову", "app.submenu.application.noLocaleOptionLabel": "ВідÑутні переклади", - "app.submenu.audio.micSourceLabel": "ПриÑтрій запиÑу", + "app.submenu.notification.SectionTitle": "СповіщеннÑ", + "app.submenu.notification.Desc": "Оберіть Ñк та про що Ð²Ð°Ñ Ð±ÑƒÐ´Ðµ Ñповіщено", + "app.submenu.notification.audioAlertLabel": "Звукові ÑповіщеннÑ", + "app.submenu.notification.pushAlertLabel": "Виринаючі ÑповіщеннÑ", + "app.submenu.notification.messagesLabel": "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñƒ чаті", + "app.submenu.notification.userJoinLabel": "ПриєднавÑÑ ÑƒÑ‡Ð°Ñник", + "app.submenu.audio.micSourceLabel": "Джерело мікрофона", "app.submenu.audio.speakerSourceLabel": "ПриÑтрій відтвореннÑ", - "app.submenu.audio.streamVolumeLabel": "ГучніÑть звукового потоку", + "app.submenu.audio.streamVolumeLabel": "ГучніÑть звука", "app.submenu.video.title": "Відео", "app.submenu.video.videoSourceLabel": "Джерело відео", "app.submenu.video.videoOptionLabel": "Виберіть джерело відео", @@ -318,12 +325,12 @@ "app.settings.main.save.label": "Зберегти", "app.settings.main.save.label.description": "Зберігає зміни та закриває меню налаштувань", "app.settings.dataSavingTab.label": "Ð—Ð°Ð¾Ñ‰Ð°Ð´Ð¶ÐµÐ½Ð½Ñ Ñ‚Ñ€Ð°Ñ„Ñ–ÐºÑƒ", - "app.settings.dataSavingTab.webcam": "Вебкамери учаÑників", + "app.settings.dataSavingTab.webcam": "Дозволити вебкамери", "app.settings.dataSavingTab.screenShare": "ДемонÑÑ‚Ñ€Ð°Ñ†Ñ–Ñ ÐµÐºÑ€Ð°Ð½Ñƒ", "app.settings.dataSavingTab.description": "Ð”Ð»Ñ Ð·Ð°Ð¾Ñ‰Ð°Ð´Ð¶ÐµÐ½Ð½Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ñ– даних, будь лаÑка, вимкніть функції, Ñкі пов'Ñзані з демонÑтрацією відео:", "app.settings.save-notification.label": "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð¾", - "app.switch.onLabel": "ТÐК", - "app.switch.offLabel": "ÐІ", + "app.switch.onLabel": "УВІМК", + "app.switch.offLabel": "ВИМК", "app.talkingIndicator.ariaMuteDesc" : "ÐатиÑніть, щоб вимкнути мікрофон учаÑника", "app.talkingIndicator.isTalking" : "{0} говорить", "app.talkingIndicator.wasTalking" : "{0} закінчив говорити", @@ -348,25 +355,25 @@ "app.actionsBar.actionsDropdown.takePresenterDesc": "Ð’Ñтановити Ñебе ведучим/презентатором", "app.actionsBar.emojiMenu.statusTriggerLabel": "СтатуÑ", "app.actionsBar.emojiMenu.awayLabel": "Відійшов", - "app.actionsBar.emojiMenu.awayDesc": "Змінює ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"Відійшов\"", + "app.actionsBar.emojiMenu.awayDesc": "Змінити ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"Відійшов\\\"", "app.actionsBar.emojiMenu.raiseHandLabel": "Бажаю говорити", "app.actionsBar.emojiMenu.raiseHandDesc": "ПіднÑти руку, щоб поÑтавити питаннÑ", "app.actionsBar.emojiMenu.neutralLabel": "Ðе визначивÑÑ", - "app.actionsBar.emojiMenu.neutralDesc": "Змінює ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"Ðе визначивÑÑ\"", + "app.actionsBar.emojiMenu.neutralDesc": "Змінити ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"Ðе визначивÑÑ\\\"", "app.actionsBar.emojiMenu.confusedLabel": "Збентежений", - "app.actionsBar.emojiMenu.confusedDesc": "Змінює ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"Збентежений\"", + "app.actionsBar.emojiMenu.confusedDesc": "Змінити ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"Збентежений\\\"", "app.actionsBar.emojiMenu.sadLabel": "Сумний", - "app.actionsBar.emojiMenu.sadDesc": "Змінює ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"Сумний\"", + "app.actionsBar.emojiMenu.sadDesc": "Змінити ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"Сумний\\\"", "app.actionsBar.emojiMenu.happyLabel": "РадіÑний", - "app.actionsBar.emojiMenu.happyDesc": "Змінює ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"РадіÑний\"", + "app.actionsBar.emojiMenu.happyDesc": "Змінити ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"РадіÑний\\\"", "app.actionsBar.emojiMenu.noneLabel": "ЗнÑти ÑтатуÑ", "app.actionsBar.emojiMenu.noneDesc": "Знімає ваш ÑтатуÑ", "app.actionsBar.emojiMenu.applauseLabel": "ОплеÑки", - "app.actionsBar.emojiMenu.applauseDesc": "Змінює ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"ОплеÑки\"", + "app.actionsBar.emojiMenu.applauseDesc": "Змінити ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"ОплеÑки\\\"", "app.actionsBar.emojiMenu.thumbsUpLabel": "ПодобаєтьÑÑ", - "app.actionsBar.emojiMenu.thumbsUpDesc": "Змінює ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"ПодобаєтьÑÑ\"", + "app.actionsBar.emojiMenu.thumbsUpDesc": "Змінити ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"ПодобаєтьÑÑ\\\"", "app.actionsBar.emojiMenu.thumbsDownLabel": "Ðе подобаєтьÑÑ", - "app.actionsBar.emojiMenu.thumbsDownDesc": "Змінює ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \"Ðе подобаєтьÑÑ\"", + "app.actionsBar.emojiMenu.thumbsDownDesc": "Змінити ваш ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° \\\"Ðе подобаєтьÑÑ\\\"", "app.actionsBar.currentStatusDesc": "поточний ÑÑ‚Ð°Ñ‚ÑƒÑ {0}", "app.actionsBar.captions.start": "Почати переглÑд Ñубтитрів", "app.actionsBar.captions.stop": "Зупинити переглÑд Ñубтитрів", @@ -385,14 +392,14 @@ "app.audioNotification.audioFailedMessage": "Ðе вдалоÑÑ Ð²Ñтановити голоÑове з'єднаннÑ", "app.audioNotification.mediaFailedMessage": "Помилка getUserMicMedia, дозволені тільки безпечні джерела", "app.audioNotification.closeLabel": "Закрити", - "app.audioNotificaion.reconnectingAsListenOnly": "Ведучий вимкнув учаÑникам мікрофон, ви можете приєднатиÑÑ Ð»Ð¸ÑˆÐµ Ñк Ñлухач", - "app.breakoutJoinConfirmation.title": "ПриєднатиÑÑ Ð´Ð¾ зуÑтрічі", + "app.audioNotificaion.reconnectingAsListenOnly": "Мікрофони вимкнено Ð´Ð»Ñ Ð³Ð»Ñдачів, ви під'єднані Ñк Ñлухач", + "app.breakoutJoinConfirmation.title": "ПриєднатиÑÑ Ð´Ð¾ кімнати перерви", "app.breakoutJoinConfirmation.message": "Чи хочете ви приєднатиÑÑ Ð´Ð¾", - "app.breakoutJoinConfirmation.confirmDesc": "Приєднує Ð²Ð°Ñ Ð´Ð¾ зуÑтрічі", + "app.breakoutJoinConfirmation.confirmDesc": "Приєднує Ð²Ð°Ñ Ð´Ð¾ кімнати перерви", "app.breakoutJoinConfirmation.dismissLabel": "СкаÑувати", - "app.breakoutJoinConfirmation.dismissDesc": "Закрити та відмовити у приєднанні до зуÑтрічі", - "app.breakoutJoinConfirmation.freeJoinMessage": "Виберіть конференцію, до Ñкої бажаєте під’єднатиÑÑ", - "app.breakoutTimeRemainingMessage": "Ð§Ð°Ñ Ð´Ð¾ Ð·Ð°ÐºÑ–Ð½Ñ‡ÐµÐ½Ð½Ñ ÐºÐ¾Ð½Ñ„ÐµÑ€ÐµÐ½Ñ†Ñ–Ñ—: {0}", + "app.breakoutJoinConfirmation.dismissDesc": "Закрити та відмовити у приєднанні до кімнати перерви", + "app.breakoutJoinConfirmation.freeJoinMessage": "Виберіть кімнату Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ€Ð²Ð¸ , до Ñкої бажаєте під’єднатиÑÑ", + "app.breakoutTimeRemainingMessage": "Ð§Ð°Ñ Ð¿ÐµÑ€ÐµÑ€Ð²Ð¸ у кімнаті : {0}", "app.breakoutWillCloseMessage": "Ð§Ð°Ñ Ð²Ð¸Ñ‡ÐµÑ€Ð¿Ð°Ð½Ð¾. Конференцію невдовзі буде закрито", "app.calculatingBreakoutTimeRemaining": "Підрахунок чаÑу, що залишивÑÑ...", "app.audioModal.ariaTitle": "Вікно Ð¿Ñ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ð´Ð¾ голоÑової конференції", @@ -403,16 +410,16 @@ "app.audioModal.iOSErrorDescription": "Поки що звук та відео у Chrome Ð´Ð»Ñ iOS не підтримуютьÑÑ.", "app.audioModal.iOSErrorRecommendation": "Ми рекомендуємо викориÑтовувати Safari Ð´Ð»Ñ iOS.", "app.audioModal.audioChoiceDesc": "Виберіть ÑпоÑіб учаÑті у голоÑовій конференції", - "app.audioModal.unsupportedBrowserLabel": "Схоже, ви викориÑтовуєте переглÑдач, Ñкий повніÑтю не підтримуєтьÑÑ. Ð”Ð»Ñ Ð¿Ð¾Ð²Ð½Ð¾Ñ— підтримки викориÑтовуйте {0} або {1}.", + "app.audioModal.unsupportedBrowserLabel": "Схоже, ви викориÑтовуєте браузер, Ñкий повніÑтю не підтримуєтьÑÑ. Ð”Ð»Ñ Ð¿Ð¾Ð²Ð½Ð¾Ñ— підтримки викориÑтовуйте {0} або {1}.", "app.audioModal.closeLabel": "Закрити", "app.audioModal.yes": "Так", - "app.audioModal.no": "Змінити приÑтрій", + "app.audioModal.no": "ÐÑ–", "app.audioModal.yes.arialabel" : "Чутно луну", "app.audioModal.no.arialabel" : "Луну не чутно", - "app.audioModal.echoTestTitle": "Перевірка звукових приÑтроїв.\nБудь лаÑка, Ñкажіть щоÑÑŒ. Чи ви чуєте Ñебе?", - "app.audioModal.settingsTitle": "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð²ÑƒÐºÐ¾Ð²Ð¸Ñ… приÑтроїв", - "app.audioModal.helpTitle": "З'ÑвилиÑÑ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð¸ з приÑтроÑми Ð²Ñ–Ð´Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð²ÑƒÐºÑƒ", - "app.audioModal.helpText": "Чи ви надали BigBlueButton дозвіл на доÑтуп до мікрофона? Зверніть увагу, що коли ви намагаєтеÑÑ Ð¿Ñ€Ð¸Ñ”Ð´Ð½Ð°Ñ‚Ð¸ÑÑ Ð´Ð¾ голоÑової конференції, має з'ÑвитиÑÑ Ð´Ñ–Ð°Ð»Ð¾Ð³Ð¾Ð²Ðµ вікно, в Ñкому Ð²Ð°Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ð°ÑŽÑ‚ÑŒ про дозвіл на під'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð¼ÑƒÐ»ÑŒÑ‚Ð¸Ð¼ÐµÐ´Ñ–Ð¹Ð½Ð¸Ñ… приÑтроїв. Будь лаÑка, прийміть це, щоб вÑтановити голоÑовий зв'Ñзок. Якщо цього не відбулоÑÑ Ñпробуйте змінити дозволи мікрофона у налаштуваннÑÑ… вашого переглÑдача.", + "app.audioModal.echoTestTitle": "Перевірка звука. Будь лаÑка, Ñкажіть щоÑÑŒ. Чи ви чуєте Ñебе?", + "app.audioModal.settingsTitle": "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð²ÑƒÐºÑƒ", + "app.audioModal.helpTitle": "З'ÑвилиÑÑ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð¸ з вашими медіа приÑтроÑми", + "app.audioModal.helpText": "Чи ви надали дозвіл на доÑтуп до мікрофона? Зверніть увагу, що коли ви намагаєтеÑÑ Ð¿Ñ€Ð¸Ñ”Ð´Ð½Ð°Ñ‚Ð¸ÑÑ Ð´Ð¾ голоÑової конференції, має з'ÑвитиÑÑ Ð´Ñ–Ð°Ð»Ð¾Ð³Ð¾Ð²Ðµ вікно, в Ñкому Ð²Ð°Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ð°ÑŽÑ‚ÑŒ про дозвіл на під'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð¼ÑƒÐ»ÑŒÑ‚Ð¸Ð¼ÐµÐ´Ñ–Ð¹Ð½Ð¸Ñ… приÑтроїв. Будь лаÑка, прийміть це, щоб вÑтановити голоÑовий зв'Ñзок. Якщо цього не відбулоÑÑ Ñпробуйте змінити дозволи мікрофона у налаштуваннÑÑ… вашого переглÑдача.", "app.audioModal.help.noSSL": "Сторінка незахищена. Щоб дозволити доÑтуп до мікрофона, Ñторінка повинна обÑлуговуватиÑÑ Ñ‡ÐµÑ€ÐµÐ· HTTPS. Будь лаÑка, зв'ÑжітьÑÑ Ð· адмініÑтратором Ñервера.", "app.audioModal.help.macNotAllowed": "Схоже, ÑиÑтемні Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Mac блокують доÑтуп до вашого мікрофону. Відкрийте System Preferences > Security & Privacy > Privacy > Microphone та переконайтеÑÑ, що вибрано переглÑдач, Ñким ви кориÑтуєтеÑÑ.", "app.audioModal.audioDialTitle": "ПриєднатиÑÑ Ð·Ð° допомогою телефону", @@ -435,13 +442,13 @@ "app.audioManager.invalidTarget": "Помилка: Спроба запроÑити до неправильного призначеннÑ", "app.audioManager.mediaError": "Помилка: Виникли проблеми з приÑтроÑми Ð²Ñ–Ð´Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð²ÑƒÐºÑƒ", "app.audio.joinAudio": "ПриєднатиÑÑ Ð´Ð¾ голоÑової конференції ", - "app.audio.leaveAudio": "Вийти", + "app.audio.leaveAudio": "Вийти з аудіо конференції", "app.audio.enterSessionLabel": "ПриєднатиÑÑ Ð´Ð¾ ÑеанÑу", "app.audio.playSoundLabel": "Відтворити звук", "app.audio.backLabel": "Ðазад", "app.audio.audioSettings.titleLabel": "Виберіть Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð²ÑƒÐºÑƒ", - "app.audio.audioSettings.descriptionLabel": "У вашому переглÑдачі з'ÑвитьÑÑ Ð²Ñ–ÐºÐ½Ð¾ із запитом на доÑтуп до мікрофона. Вам потрібно його підтвердити.", - "app.audio.audioSettings.microphoneSourceLabel": "ПриÑтрій запиÑу", + "app.audio.audioSettings.descriptionLabel": "У вашому браузері з'ÑвитьÑÑ Ð²Ñ–ÐºÐ½Ð¾ із запитом на доÑтуп до мікрофона. Вам потрібно його підтвердити.", + "app.audio.audioSettings.microphoneSourceLabel": "Джерело мікрофона", "app.audio.audioSettings.speakerSourceLabel": "ПриÑтрій відтвореннÑ", "app.audio.audioSettings.microphoneStreamLabel": "ГучніÑть вашого звукового потоку", "app.audio.audioSettings.retryLabel": "Повторити", @@ -475,24 +482,26 @@ "app.error.fallback.presentation.description": "Уже увійшли. Спробуйте перезавантажити Ñторінку.", "app.error.fallback.presentation.reloadButton": "Перезавантажити", "app.guest.waiting": "ÐžÑ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ ÑÑ…Ð²Ð°Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¸Ñ”Ð´Ð½Ð°Ð½Ð½Ñ", - "app.userList.guest.waitingUsers": "Очікуємо на учаÑників", + "app.userList.guest.waitingUsers": "УчаÑники у очікуванні", "app.userList.guest.waitingUsersTitle": "ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ ÑƒÑ‡Ð°Ñниками", "app.userList.guest.optionTitle": "ПереглÑнути учаÑників, Ñкі очікують", "app.userList.guest.allowAllAuthenticated": "Дозволити вÑім авторизованим", "app.userList.guest.allowAllGuests": "Дозволити вÑім гоÑÑ‚Ñм", "app.userList.guest.allowEveryone": "Дозволити вÑім", "app.userList.guest.denyEveryone": "Заборонити вÑім", - "app.userList.guest.pendingUsers": "{0} уваÑники в очікуванні", + "app.userList.guest.pendingUsers": "{0} учаÑників у очікуванні", "app.userList.guest.pendingGuestUsers": "{0} гоÑтей в очікуванні", "app.userList.guest.pendingGuestAlert": "ПриєднавÑÑ Ð´Ð¾ ÑеанÑу та очікує вашого ÑхваленнÑ", "app.userList.guest.rememberChoice": "Запам'Ñтати вибір", + "app.userList.guest.acceptLabel": "ПрийнÑти", + "app.userList.guest.denyLabel": "Відмовити", "app.user-info.title": "Пошук у каталозі", "app.toast.breakoutRoomEnded": "ÐšÐ¾Ð½Ñ„ÐµÑ€ÐµÐ½Ñ†Ñ–Ñ Ð·Ð°ÐºÑ–Ð½Ñ‡Ð¸Ð»Ð°ÑÑ. Будь лаÑка, приєднайтеÑÑŒ знову до аудіо конференції.", "app.toast.chat.public": "Ðове Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñƒ загальному чаті", "app.toast.chat.private": "Ðове Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñƒ приватному чаті", "app.toast.chat.system": "СиÑтема", "app.toast.clearedEmoji.label": "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð·Ð½Ñто", - "app.toast.setEmoji.label": "{0}", + "app.toast.setEmoji.label": "УÑÑ– ÑтатуÑи вÑтановлені на {0}", "app.toast.meetingMuteOn.label": "Ð’Ñім учаÑникам вимкнено мікрофони", "app.toast.meetingMuteOff.label": "Ð‘Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð¼Ñ–ÐºÑ€Ð¾Ñ„Ð¾Ð½Ñƒ вимкнено", "app.notification.recordingStart": "Цей ÑÐµÐ°Ð½Ñ Ð·Ð°Ð¿Ð¸ÑуєтьÑÑ", @@ -500,41 +509,48 @@ "app.notification.recordingPaused": "Цей ÑÐµÐ°Ð½Ñ Ð±Ñ–Ð»ÑŒÑˆÐµ не запиÑуєтьÑÑ", "app.notification.recordingAriaLabel": "ЗапиÑано чаÑу ", "app.notification.userJoinPushAlert": "{0} приєднавÑÑ Ð´Ð¾ ÑеанÑу", + "app.submenu.notification.raiseHandLabel": "ПіднÑти руку", "app.shortcut-help.title": "ГарÑчі клавіші", - "app.shortcut-help.accessKeyNotAvailable": "Клавіші швидкого доÑтупу недоÑтупні.", + "app.shortcut-help.accessKeyNotAvailable": "Клавіші швидкого доÑтупу недоÑтупні", "app.shortcut-help.comboLabel": "Комбо", "app.shortcut-help.functionLabel": "ФункціÑ", "app.shortcut-help.closeLabel": "Закрити", - "app.shortcut-help.closeDesc": "Закрити вікно 'гарÑчих' клавіш", + "app.shortcut-help.closeDesc": "Закрити вікно \\\"гарÑчих\\\" клавіш", "app.shortcut-help.openOptions": "Відкрити налаштуваннÑ", "app.shortcut-help.toggleUserList": "Перемкнути ÑпиÑок учаÑників", - "app.shortcut-help.toggleMute": "Змінити Ñтан мікрофону", + "app.shortcut-help.toggleMute": "Мікрофон: Вимкн/Увімкн", "app.shortcut-help.togglePublicChat": "Увімкнути загальний чат (вікно зі ÑпиÑком учаÑників має бути відкрито)", "app.shortcut-help.hidePrivateChat": "Приховати приватний чат", "app.shortcut-help.closePrivateChat": "Закрити приватний чат", "app.shortcut-help.openActions": "Відкрити меню дій", - "app.shortcut-help.openStatus": "Відкрити меню ÑтатуÑів", - "app.shortcut-help.togglePan": "Ðктивувати Ñпільний доÑтуп до інÑтрументів (ведучий)", - "app.shortcut-help.nextSlideDesc": "Перейти на наÑтупний Ñлайд (ведучий)", - "app.shortcut-help.previousSlideDesc": "ПовернутиÑÑ Ð´Ð¾ попереднього Ñлайду (ведучий)", - "app.lock-viewers.title": "ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ñ„ÑƒÐ½ÐºÑ†Ñ–Ð¹ учаÑникам", + "app.shortcut-help.openStatus": "Відкрити ÑÑ‚Ð°Ñ‚ÑƒÑ Ð¼ÐµÐ½ÑŽ ", + "app.shortcut-help.togglePan": "Включити ІнÑтрумент - ÐŸÐµÑ€ÐµÐ¼Ñ–Ñ‰ÐµÐ½Ð½Ñ (ведучий)", + "app.shortcut-help.nextSlideDesc": "ÐаÑтупний Ñлайд (ведучий)", + "app.shortcut-help.previousSlideDesc": "Попередній Ñлайд (ведучий)", + "app.lock-viewers.title": "Обмежити глÑдачів", "app.lock-viewers.description": "Ці Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð¾Ð·Ð²Ð¾Ð»Ñють обмежити учаÑників у доÑтупі до певних функцій", - "app.lock-viewers.featuresLable": "ФункціÑ", + "app.lock-viewers.featuresLable": "ОÑобливіÑть", "app.lock-viewers.lockStatusLabel": "Стан", "app.lock-viewers.webcamLabel": "Увімкнути вебкамеру", - "app.lock-viewers.otherViewersWebcamLabel": "Бачити вебкамери інших учаÑників", - "app.lock-viewers.microphoneLable": "Вмикати Ñвій мікрофон", - "app.lock-viewers.PublicChatLabel": "ÐадÑилати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñƒ загальному чаті", - "app.lock-viewers.PrivateChatLable": "ÐадÑилати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñƒ приватному чаті", + "app.lock-viewers.otherViewersWebcamLabel": "ДивитиÑÑ Ð²ÐµÐ±ÐºÐ°Ð¼ÐµÑ€Ð¸ інших учаÑників", + "app.lock-viewers.microphoneLable": "Увімкнути влаÑний мікрофон", + "app.lock-viewers.PublicChatLabel": "ÐадіÑлати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñƒ загальному чаті", + "app.lock-viewers.PrivateChatLable": "ÐадіÑлати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñƒ приватному чаті", "app.lock-viewers.notesLabel": "Редагувати Ñпільні нотатки", - "app.lock-viewers.userListLabel": "ПереглÑдати учаÑників у ÑпиÑку учаÑників", + "app.lock-viewers.userListLabel": "Показувати інших глÑдачів у ÑпиÑку кориÑтувачів", "app.lock-viewers.ariaTitle": "Вікно Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð³Ð¾Ñтей", "app.lock-viewers.button.apply": "ЗаÑтоÑувати", "app.lock-viewers.button.cancel": "СкаÑувати", - "app.lock-viewers.locked": "Обмежено", + "app.lock-viewers.locked": "Заблокований", "app.lock-viewers.unlocked": "Розблокований", + "app.connection-status.ariaTitle": "Вікно умов Ñтану з'єднаннÑ", + "app.connection-status.title": "Стан з'єднаннÑ", + "app.connection-status.description": "ПереглÑд Ñтану з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувачів", + "app.connection-status.empty": "До цього відÑутні проблеми зі з'єднаннÑм", + "app.connection-status.more": "докладно", + "app.connection-status.offline": "офлайн", "app.recording.startTitle": "Почати запиÑ", - "app.recording.stopTitle": "Пауза", + "app.recording.stopTitle": "Призупинити запиÑ", "app.recording.resumeTitle": "Відновити запиÑ", "app.recording.startDescription": "Ð”Ð»Ñ Ð¿Ð°ÑƒÐ·Ð¸ запиÑу клацніть повторно на кнопку запиÑу.", "app.recording.stopDescription": "ДійÑно призупинити запиÑ? Щоб відновити запиÑ, клацніть ще раз на кнопку запиÑу.", @@ -543,12 +559,14 @@ "app.videoPreview.cancelLabel": "СкаÑувати", "app.videoPreview.closeLabel": "Закрити", "app.videoPreview.findingWebcamsLabel": "Пошук вебкамер", - "app.videoPreview.startSharingLabel": "Почати транÑлÑцію", + "app.videoPreview.startSharingLabel": "Почати демонÑтрацію", + "app.videoPreview.stopSharingLabel": "Припинити демонÑтрацію", + "app.videoPreview.sharedCameraLabel": "Ð¦Ñ ÐºÐ°Ð¼ÐµÑ€Ð° вже викориÑтовуєтьÑÑ", "app.videoPreview.webcamOptionLabel": "Виберіть вебкамеру", "app.videoPreview.webcamPreviewLabel": "Попередній переглÑд вебкамери", "app.videoPreview.webcamSettingsTitle": "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð²ÐµÐ±ÐºÐ°Ð¼ÐµÑ€Ð¸", "app.videoPreview.webcamNotFoundLabel": "Вебкамеру не знайдено", - "app.videoPreview.profileNotFoundLabel": "Ðе знайдено підтримувану вебкамеру", + "app.videoPreview.profileNotFoundLabel": "Камера не відповідає підтримуваним", "app.video.joinVideo": "Увімкнути вебкамеру", "app.video.leaveVideo": "Вимкнути вебкамеру", "app.video.iceCandidateError": "Помилка Ð´Ð¾Ð´Ð°Ð²Ð°Ð½Ð½Ñ ICE кандидату", @@ -559,34 +577,21 @@ "app.video.notAllowed": "ВідÑутній дозвіл на транÑлÑцію вебкамери, будь лаÑка, переконайтеÑÑ, що ваш переглÑдач має необхідні дозволи", "app.video.notSupportedError": "ДозволÑєтьÑÑ Ñ‚Ñ€Ð°Ð½Ñлювати потік з вебкамери лише з безпечних джерел, переконайтеÑÑ, що Ñертифікат SSL дійÑний", "app.video.notReadableError": "Ðе вдалоÑÑ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ñ‚Ð¸ відео з вебкамери. Будь лаÑка, переконайтеÑÑ, що інша програма не викориÑтовує Ñ—Ñ—", - "app.video.mediaFlowTimeout1020": "Мультимедії не доÑÑгають Ñервера (помилка 1020)", - "app.video.suggestWebcamLock": "ПримуÑово заблокувати вебкамери учаÑникам?", + "app.video.mediaFlowTimeout1020": "Потоки не доÑÑгають Ñервера (помилка 1020)", + "app.video.suggestWebcamLock": "ПримуÑово заблокувати Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð»Ñ Ð³Ð»Ñдачів?", "app.video.suggestWebcamLockReason": "(це підвищить ÑтабільніÑть конференції)", "app.video.enable": "Увімкнути", "app.video.cancel": "СкаÑувати", - "app.video.swapCam": "Змінити", - "app.video.swapCamDesc": "змінити Ñ„Ð¾ÐºÑƒÑ Ð²ÐµÐ±ÐºÐ°Ð¼ÐµÑ€Ð¸", + "app.video.swapCam": "переключити", + "app.video.swapCamDesc": "Переключити напрÑм вебкамер", "app.video.videoLocked": "ТранÑлÑцію вебкамери заблоковано", "app.video.videoButtonDesc": "Увімкнути вебкамеру", - "app.video.videoMenu": "Меню відео", - "app.video.videoMenuDisabled": "Меню відео: вебкамеру вимкнено у налаштуваннÑÑ…", + "app.video.videoMenu": "Відео меню", + "app.video.videoMenuDisabled": "Відео меню вебкамери вимкнено у налаштуваннÑÑ…", "app.video.videoMenuDesc": "Відкрити контекÑтне меню відео", "app.video.chromeExtensionError": "Ви маєте вÑтановити", "app.video.chromeExtensionErrorLink": "це Ñ€Ð¾Ð·ÑˆÐ¸Ñ€ÐµÐ½Ð½Ñ Chrome", - "app.video.stats.title": "СтатиÑтика з'єднань", - "app.video.stats.packetsReceived": "Отримані пакети", - "app.video.stats.packetsSent": "ÐадіÑлані пакети", - "app.video.stats.packetsLost": "Втрачені пакети", - "app.video.stats.bitrate": "Бітрейт", - "app.video.stats.lostPercentage": "Загальний відÑоток втрачених", - "app.video.stats.lostRecentPercentage": "Ðинішній відÑоток втрачених", - "app.video.stats.dimensions": "Розміри", - "app.video.stats.codec": "Кодек", - "app.video.stats.decodeDelay": "Затримка декодуваннÑ", - "app.video.stats.rtt": "Ð§Ð°Ñ RTT", - "app.video.stats.encodeUsagePercent": "ВикориÑÑ‚Ð°Ð½Ð½Ñ ÐºÐ¾Ð´ÑƒÐ²Ð°Ð½Ð½Ñ", - "app.video.stats.currentDelay": "Поточна затримка", - "app.fullscreenButton.label": "{0} на веÑÑŒ екран", + "app.fullscreenButton.label": "ВивеÑти {0} на веÑÑŒ екран", "app.deskshare.iceConnectionStateError": "Ðе вдалоÑÑ Ð²Ñтановити з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð¿Ñ–Ð´ Ñ‡Ð°Ñ Ð´ÐµÐ¼Ð¾Ð½Ñтрації екрану (ICE помилка 1108)", "app.sfu.mediaServerConnectionError2000": "Ðе можливо з'єднатиÑÑ Ð· мультимедійним Ñервером (помилка 2000)", "app.sfu.mediaServerOffline2001": "Мультимедійний Ñервер недоÑтупний. Будь лаÑка Ñпробуйте пізніше (помилка 2001)", @@ -597,95 +602,94 @@ "app.sfu.mediaGenericError2200": "Мультимедійний Ñервер не зміг обробити запит (помилка 2200)", "app.sfu.invalidSdp2202":"Клієнт Ñформував пошкоджений медіа запит (SDP помилка 2202)", "app.sfu.noAvailableCodec2203": "Сервер не може визначити відповідний кодек (помилка 2203)", - "app.meeting.endNotification.ok.label": "Гаразд", - "app.whiteboard.annotations.poll": "Результати Ð¾Ð¿Ð¸Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð¿ÑƒÐ±Ð»Ñ–ÐºÐ¾Ð²Ð°Ð½Ð¾", + "app.meeting.endNotification.ok.label": "ОК", "app.whiteboard.toolbar.tools": "ІнÑтрументи", "app.whiteboard.toolbar.tools.hand": "ПереміщеннÑ", "app.whiteboard.toolbar.tools.pencil": "Олівець", "app.whiteboard.toolbar.tools.rectangle": "Чотирикутник", "app.whiteboard.toolbar.tools.triangle": "Трикутник", - "app.whiteboard.toolbar.tools.ellipse": "Коло", + "app.whiteboard.toolbar.tools.ellipse": "ЕліпÑ", "app.whiteboard.toolbar.tools.line": "ЛініÑ", "app.whiteboard.toolbar.tools.text": "ТекÑÑ‚", - "app.whiteboard.toolbar.thickness": "Товщина", - "app.whiteboard.toolbar.thicknessDisabled": "Товщина Ð¼Ð°Ð»ÑŽÐ²Ð°Ð½Ð½Ñ Ð½ÐµÐ°ÐºÑ‚Ð¸Ð²Ð½Ð°", - "app.whiteboard.toolbar.color": "Колір", - "app.whiteboard.toolbar.colorDisabled": "Кольори неактивні", + "app.whiteboard.toolbar.thickness": "Товщина малюваннÑ", + "app.whiteboard.toolbar.thicknessDisabled": "Товщина Ð¼Ð°Ð»ÑŽÐ²Ð°Ð½Ð½Ñ Ð·Ð°Ð±Ð¾Ñ€Ð¾Ð½ÐµÐ½Ð°", + "app.whiteboard.toolbar.color": "Кольори", + "app.whiteboard.toolbar.colorDisabled": "Кольори заборонені", "app.whiteboard.toolbar.color.black": "Чорний", "app.whiteboard.toolbar.color.white": "Білий", "app.whiteboard.toolbar.color.red": "Червоний", "app.whiteboard.toolbar.color.orange": "Помаранчевий", - "app.whiteboard.toolbar.color.eletricLime": "Салатовий", - "app.whiteboard.toolbar.color.lime": "Зелений", - "app.whiteboard.toolbar.color.cyan": "Бірюзовий", - "app.whiteboard.toolbar.color.dodgerBlue": "Блакитний", + "app.whiteboard.toolbar.color.eletricLime": "Світлий лайм", + "app.whiteboard.toolbar.color.lime": "Лаймовий", + "app.whiteboard.toolbar.color.cyan": "Ціан", + "app.whiteboard.toolbar.color.dodgerBlue": "Лазуровий", "app.whiteboard.toolbar.color.blue": "Синій", "app.whiteboard.toolbar.color.violet": "Фіолетовий", - "app.whiteboard.toolbar.color.magenta": "Пурпурний", - "app.whiteboard.toolbar.color.silver": "Сірий", - "app.whiteboard.toolbar.undo": "СкаÑувати", - "app.whiteboard.toolbar.clear": "Стерти вÑе", - "app.whiteboard.toolbar.multiUserOn": "Увімкнути Ñпільний доÑтуп", - "app.whiteboard.toolbar.multiUserOff": "Вимкнути Ñпільний доÑтуп", - "app.whiteboard.toolbar.fontSize": "Розміру шрифту", + "app.whiteboard.toolbar.color.magenta": "Пурпуровий", + "app.whiteboard.toolbar.color.silver": "Срібний", + "app.whiteboard.toolbar.undo": "СкаÑувати нотатку", + "app.whiteboard.toolbar.clear": "Стерти уÑÑ– нотатки", + "app.whiteboard.toolbar.multiUserOn": "Увімкнути Ñпільний доÑтуп до дошки", + "app.whiteboard.toolbar.multiUserOff": "Вимкнути Ñпільний доÑтуп до дошки", + "app.whiteboard.toolbar.fontSize": "СпиÑок розмірів шрифтів", "app.feedback.title": "Ви вийшли з конференції", "app.feedback.subtitle": "Будь лаÑка, поділітьÑÑ Ð²Ð°ÑˆÐ¸Ð¼ доÑвідом кориÑÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ BigBlueButton (необов'Ñзково)", "app.feedback.textarea": "Як можна покращити BigBlueButton?", "app.feedback.sendFeedback": "ÐадіÑлати відгук", "app.feedback.sendFeedbackDesc": "ÐадіÑлати відгук та вийти", - "app.videoDock.webcamFocusLabel": "ФокуÑ", - "app.videoDock.webcamFocusDesc": "СфокуÑувати вибрану вебкамеру", - "app.videoDock.webcamUnfocusLabel": "Змінити фокуÑ", - "app.videoDock.webcamUnfocusDesc": "Змінити Ñ„Ð¾ÐºÑƒÑ Ð²ÐµÐ±ÐºÐ°Ð¼ÐµÑ€Ð¸", - "app.videoDock.autoplayBlockedDesc": "Ðам потрібен дозвіл, щоб показати вам вебкамери інших учаÑників.", - "app.videoDock.autoplayAllowLabel": "ПодивитиÑÑ Ð²ÐµÐ±ÐºÐ°Ð¼ÐµÑ€Ð¸", - "app.invitation.title": "Ð—Ð°Ð¿Ñ€Ð¾ÑˆÐµÐ½Ð½Ñ ÑƒÑ‡Ð°Ñників до окремих кімнат", + "app.videoDock.webcamFocusLabel": "Ðкцент", + "app.videoDock.webcamFocusDesc": "Ðкцентувати обрану камеру", + "app.videoDock.webcamUnfocusLabel": "Ðе акцентувати", + "app.videoDock.webcamUnfocusDesc": "Ðе акцентувати обрану камеру", + "app.videoDock.autoplayBlockedDesc": "Потрібен дозвіл, щоб показати вам вебкамери інших учаÑників.", + "app.videoDock.autoplayAllowLabel": "ДивитиÑÑ Ð²ÐµÐ±ÐºÐ°Ð¼ÐµÑ€Ð¸", + "app.invitation.title": "Ð—Ð°Ð¿Ñ€Ð¾ÑˆÐµÐ½Ð½Ñ Ð´Ð¾ кімнати перерв ", "app.invitation.confirm": "ЗапроÑити", - "app.createBreakoutRoom.title": "Кімнати учаÑників", - "app.createBreakoutRoom.ariaTitle": "Приховати кімнати учаÑників", - "app.createBreakoutRoom.breakoutRoomLabel": "Кімнати учаÑників {0}", + "app.createBreakoutRoom.title": "Кімнати Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ€Ð²", + "app.createBreakoutRoom.ariaTitle": "Приховати кімнати Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ€Ð²", + "app.createBreakoutRoom.breakoutRoomLabel": "Кімнати Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ€Ð² {0}", "app.createBreakoutRoom.generatingURL": "Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ URL", "app.createBreakoutRoom.generatedURL": "Створено", "app.createBreakoutRoom.duration": "ТриваліÑть {0}", "app.createBreakoutRoom.room": "Кімната {0}", "app.createBreakoutRoom.notAssigned": "Ðе призначено ({0})", "app.createBreakoutRoom.join": "ПриєднатиÑÑ Ð´Ð¾ кімнати", - "app.createBreakoutRoom.joinAudio": "ПриєднатиÑÑ Ð´Ð¾ голоÑової конференції", + "app.createBreakoutRoom.joinAudio": "Тільки Ñлухати", "app.createBreakoutRoom.returnAudio": "Повернути звук", "app.createBreakoutRoom.alreadyConnected": "Вже у кімнаті", "app.createBreakoutRoom.confirm": "Створити", - "app.createBreakoutRoom.record": "ЗапиÑати", + "app.createBreakoutRoom.record": "ЗапиÑ", "app.createBreakoutRoom.numberOfRooms": "КількіÑть кімнат", "app.createBreakoutRoom.durationInMinutes": "ТриваліÑть (хвилини)", "app.createBreakoutRoom.randomlyAssign": "Випадково призначити", - "app.createBreakoutRoom.endAllBreakouts": "Закрити уÑÑ– кімнати учаÑників", + "app.createBreakoutRoom.endAllBreakouts": "Закрити уÑÑ– перервні кімнати ", "app.createBreakoutRoom.roomName": "{0} (Кімната - {1})", "app.createBreakoutRoom.doneLabel": "Готово", "app.createBreakoutRoom.nextLabel": "Далі", - "app.createBreakoutRoom.minusRoomTime": "Зменшити триваліÑть до", - "app.createBreakoutRoom.addRoomTime": "Збільшити триваліÑть до", + "app.createBreakoutRoom.minusRoomTime": "Зменшити триваліÑть кімнати Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ€Ð²Ð¸ до", + "app.createBreakoutRoom.addRoomTime": "Збільшити триваліÑть кімнати Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ€Ð²Ð¸ до", "app.createBreakoutRoom.addParticipantLabel": "+ Додати учаÑника", "app.createBreakoutRoom.freeJoin": "Дозволити учаÑникам обирати кімнату ÑамоÑтійно", - "app.createBreakoutRoom.leastOneWarnBreakout": "Щонайменше один учаÑникам має бути приÑутнім у кімнаті.", + "app.createBreakoutRoom.leastOneWarnBreakout": "Щонайменше один учаÑник має бути приÑутнім у кімнаті.", "app.createBreakoutRoom.modalDesc": "Примітка: Щоб призначити учаÑників до певної кімнати, будь лаÑка, перетÑгніть їхні імена до комірок кімнат.", "app.createBreakoutRoom.roomTime": "{0} хвилин", "app.createBreakoutRoom.numberOfRoomsError": "КількіÑть кімнат Ñ” неправильною.", "app.externalVideo.start": "ПоділитиÑÑ Ð½Ð¾Ð²Ð¸Ð¼ відео", - "app.externalVideo.title": "ДемонÑÑ‚Ñ€Ð°Ñ†Ñ–Ñ Ð²Ñ–Ð´ÐµÐ¾", - "app.externalVideo.input": "ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° адреÑу відеопотоку", - "app.externalVideo.urlInput": "Додати адреÑу відеопотоку", - "app.externalVideo.urlError": "Ð¦Ñ Ð°Ð´Ñ€ÐµÑа відеопотоку не підтримуєтьÑÑ", + "app.externalVideo.title": "ПоділитиÑÑ Ð·Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ–Ð¼ відео", + "app.externalVideo.input": "ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° зовнішню адреÑу відео", + "app.externalVideo.urlInput": "Додати адреÑу відео", + "app.externalVideo.urlError": "Ð¦Ñ Ð°Ð´Ñ€ÐµÑа відео не підтримуєтьÑÑ", "app.externalVideo.close": "Закрити", - "app.externalVideo.autoPlayWarning": "Відтворити відео Ð´Ð»Ñ Ñинхронізації мультимедії", + "app.externalVideo.autoPlayWarning": "Відтворити відео Ð´Ð»Ñ Ñинхронізації медіа", "app.network.connection.effective.slow": "СпоÑтерігаютьÑÑ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð¸ зі з'єднаннÑм", "app.network.connection.effective.slow.help": "Детальна інформаціÑ", - "app.externalVideo.noteLabel": "Примітка: Відеопотік зовнішніх реÑурÑів не буде показуватиÑÑ Ñƒ запиÑÑ–. ПідтримуютьÑÑ Ð¿Ð¾ÑÐ¸Ð»Ð°Ð½Ð½Ñ YouTube, Vimeo, Instructure Media, Twitch та Daily Motion.", - "app.actionsBar.actionsDropdown.shareExternalVideo": "ДемонÑÑ‚Ñ€Ð°Ñ†Ñ–Ñ Ð²Ñ–Ð´ÐµÐ¾", - "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Припинити показ зовнішнього відеопотоку", + "app.externalVideo.noteLabel": "Примітка: Зовнішні відеозапиÑи не з'ÑвлÑютьÑÑ Ñƒ запиÑÑ– ÑеанÑу. ПідтримуютьÑÑ: YouTube, Vimeo, Instructure Media, Twitch, Dailymotion та поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° відео ( наприклад : https://example.com/xy.mp4 )", + "app.actionsBar.actionsDropdown.shareExternalVideo": "ДемонÑÑ‚Ñ€Ð°Ñ†Ñ–Ñ Ð·Ð¾Ð²Ð½Ñ–ÑˆÐ½ÑŒÐ¾Ð³Ð¾ відео", + "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Припинити показ зовнішнього відео", "app.iOSWarning.label": "Будь лаÑка, оновіть приÑтрій з iOS до верÑÑ–Ñ— 12.2 або новішої верÑÑ–Ñ—", - "app.legacy.unsupportedBrowser": "Схоже, ви викориÑтовуєте переглÑдач, Ñкий в повному обÑÑзі не підтримуєтьÑÑ. Будь лаÑка, викориÑтовуйте {0} або {1} Ð´Ð»Ñ Ð¿Ð¾Ð²Ð½Ð¾Ñ— підтримки.", - "app.legacy.upgradeBrowser": "Схоже, ви викориÑтовуєте Ñтарішу верÑÑ–ÑŽ переглÑдача, Ñкий підтримуєтьÑÑ. Будь лаÑка, оновіть його Ð´Ð»Ñ Ð¿Ð¾Ð²Ð½Ð¾Ñ— підтримки.", - "app.legacy.criosBrowser": "Будь лаÑка, викориÑтовуйте переглÑдач Safari на приÑтрої з iOS Ð´Ð»Ñ Ð¿Ð¾Ð²Ð½Ð¾Ñ— підтримки." + "app.legacy.unsupportedBrowser": "Схоже, ви викориÑтовуєте браузер, Ñкий в повному обÑÑзі не підтримуєтьÑÑ. Будь лаÑка, викориÑтовуйте {0} або {1} Ð´Ð»Ñ Ð¿Ð¾Ð²Ð½Ð¾Ñ— підтримки.", + "app.legacy.upgradeBrowser": "Схоже, ви викориÑтовуєте Ñтарішу верÑÑ–ÑŽ браузера, Ñкий підтримуєтьÑÑ. Будь лаÑка, оновіть його Ð´Ð»Ñ Ð¿Ð¾Ð²Ð½Ð¾Ñ— підтримки.", + "app.legacy.criosBrowser": "Будь лаÑка, викориÑтовуйте браузер Safari на приÑтрої з iOS Ð´Ð»Ñ Ð¿Ð¾Ð²Ð½Ð¾Ñ— підтримки." } diff --git a/bigbluebutton-html5/private/locales/vi.json b/bigbluebutton-html5/private/locales/vi.json index e702e192ff12e5d34e0eff1470d9ef26edcd2fe1..d439214da39bfca24ffea25ebec12d80037d65c7 100644 --- a/bigbluebutton-html5/private/locales/vi.json +++ b/bigbluebutton-html5/private/locales/vi.json @@ -50,10 +50,10 @@ "app.note.title": "Chia sẻ ghi chú", "app.note.label": "Ghi chú", "app.note.hideNoteLabel": "Ẩn ghi chú", + "app.note.tipLabel": "Nhấn phÃm Esc để mở trình soạn thảo", "app.user.activityCheck": "Kiểm tra hoạt động cá»§a thà nh viên", "app.user.activityCheck.label": "Kiểm tra nếu thà nh viên vẫn Ä‘ang xem trò chuyện ({0})", "app.user.activityCheck.check": "Kiểm tra", - "app.note.tipLabel": "Nhấn phÃm Esc để mở trình soạn thảo", "app.userList.usersTitle": "Thà nh viên", "app.userList.participantsTitle": "Ngưá»i tham gia", "app.userList.messagesTitle": "Tin nhắn", diff --git a/bigbluebutton-html5/private/locales/vi_VN.json b/bigbluebutton-html5/private/locales/vi_VN.json index b83a24302cd977b359869ac1b7b312e52f6e82aa..793f36fdda04e4e5ee704ba91022a27f80dcd395 100644 --- a/bigbluebutton-html5/private/locales/vi_VN.json +++ b/bigbluebutton-html5/private/locales/vi_VN.json @@ -50,10 +50,10 @@ "app.note.title": "Ghi chú chung", "app.note.label": "Ghi chú", "app.note.hideNoteLabel": "Ẩn ghi chú", + "app.note.tipLabel": "Nhấn ESC để chuyển ra thanh công cụ", "app.user.activityCheck": "Kiểm tra hoạt động ngưá»i dùng", "app.user.activityCheck.label": "Kiểm tra nếu ngưá»i dùng vẫn Ä‘ang trong cuá»™c há»p ({0})", "app.user.activityCheck.check": "Kiểm tra", - "app.note.tipLabel": "Nhấn ESC để chuyển ra thanh công cụ", "app.userList.usersTitle": "Ngưá»i dùng", "app.userList.participantsTitle": "Ngưá»i tham dá»±", "app.userList.messagesTitle": "Tin nhắn", @@ -122,8 +122,6 @@ "app.meeting.meetingTimeRemaining": "Thá»i gian còn lại cá»§a cuá»™c há»p: {0}", "app.meeting.meetingTimeHasEnded": "Hết giá». Cuá»™c há»p sẽ đóng lại", "app.meeting.endedMessage": "Bạn sẽ được chuyển hướng vá» lại trang chá»§ mà n hình", - "app.meeting.alertMeetingEndsUnderOneMinute": "Cuá»™c há»p sẽ kết thúc trong má»™t phút nữa", - "app.meeting.alertBreakoutEndsUnderOneMinute": "Giải lao sẽ kết thúc trong má»™t phúc nữa ", "app.presentation.hide": "Ẩn Trình bà y", "app.presentation.notificationLabel": "Phần trình bà y hiện tại", "app.presentation.slideContent": "Ná»™i dung slide", @@ -259,7 +257,6 @@ "app.leaveConfirmation.confirmLabel": "Rá»i khá»i", "app.leaveConfirmation.confirmDesc": "Äăng xuất khá»i cuá»™c há»p", "app.endMeeting.title": "Kết thúc cuá»™c há»p", - "app.endMeeting.description": "Bạn thá»±c sá»± muốn kết thúc phiên hoạt động ?", "app.endMeeting.yesLabel": "Có", "app.endMeeting.noLabel": "Không", "app.about.title": "Giá»›i thiệu", @@ -280,10 +277,6 @@ "app.screenshare.screenShareLabel" : "Chia sẻ mà n hình", "app.submenu.application.applicationSectionTitle": "Ứng dụng", "app.submenu.application.animationsLabel": " Các hình ảnh động", - "app.submenu.application.audioAlertLabel": "Cảnh báo âm thanh cho cuá»™c trò chuyện", - "app.submenu.application.pushAlertLabel": "Cảnh báo quảng cáo cho cuá»™c trò chuyện", - "app.submenu.application.userJoinAudioAlertLabel": "Âm thanh thông báo cho ngưá»i tham gia", - "app.submenu.application.userJoinPushAlertLabel": "Popup thông báo cho ngưá»i tham gia", "app.submenu.application.fontSizeControlLabel": "Cỡ chữ", "app.submenu.application.increaseFontBtnLabel": "Tăng cỡ chữ cá»§a ứng dụng", "app.submenu.application.decreaseFontBtnLabel": "Giảm cỡ chữ cá»§a ứng dụng", @@ -402,7 +395,7 @@ "app.audioModal.playAudio": "Nghe âm thanh", "app.audioModal.playAudio.arialabel" : "Nghe âm thanh", "app.audioDial.tipIndicator": "Hướng dẫn", - "app.audioDial.tipMessage": "Ấn phÃm \"0\" trên Ä‘iện thoại để báºt/tắt âm", + "app.audioDial.tipMessage": "Ấn phÃm '0' trên Ä‘iện thoại để báºt/tắt âm", "app.audioModal.connecting": "Äang kết nối", "app.audioModal.connectingEchoTest": "Kết nối tá»›i phần kiểm tra âm thanh ", "app.audioManager.joinedAudio": "Bạn đã tham gia cuá»™ há»™i thoại âm thanh", @@ -533,7 +526,7 @@ "app.video.leaveVideo": "Dừng chia sẻ webcam", "app.video.iceCandidateError": "Error on adding ICE candidate", "app.video.permissionError": "Lá»—i chia sẻ camera, vui lòng kiểm tra quyá»n chia sẻ", - "app.video.sharingError": "\bLá»—i chia sẻ camera", + "app.video.sharingError": "Lá»—i chia sẻ camera", "app.video.notFoundError": "Không thể tìm thấy webcam. Kiểm tra lại thiết bị", "app.video.notAllowed": "Thiếu quyá»n chia sẻ webcam, kiểm tra lại quyá»n truy cáºp camera cá»§a Trình duyệt", "app.video.notSupportedError": "Chỉ có thể chia sẻ video webcam vá»›i các nguồn an toà n, dảm bảo rằng chứng chỉ SSL cá»§a bạn hợp lệ", @@ -551,22 +544,8 @@ "app.video.videoMenuDesc": "Mở thanh menu video trượt xuống", "app.video.chromeExtensionError": "Bạn phải cà i đặt", "app.video.chromeExtensionErrorLink": "tiện Ãch mở rá»™ng Chrome nà y", - "app.video.stats.title": "Các chỉ số kết nối", - "app.video.stats.packetsReceived": "Các gói đã nháºn", - "app.video.stats.packetsSent": "Các gói đã gá»i", - "app.video.stats.packetsLost": "Các gói bị mất", - "app.video.stats.bitrate": "Tốc độ bit", - "app.video.stats.lostPercentage": "Tổng phần trăm bị mất", - "app.video.stats.lostRecentPercentage": "Phần trăm bị mất gần đây", - "app.video.stats.dimensions": "KÃch thước", - "app.video.stats.codec": "Tiá»n mã hóa", - "app.video.stats.decodeDelay": "Äá»— trá»… giải mã", - "app.video.stats.rtt": "RTT", - "app.video.stats.encodeUsagePercent": "Sá» dụng mã hóa", - "app.video.stats.currentDelay": "Äá»™ trá»… hiện tại", "app.fullscreenButton.label": "Tạo {0} toà n mà n hình", "app.meeting.endNotification.ok.label": "Äồng ý", - "app.whiteboard.annotations.poll": "Äã công bố kết quả thăm dò ý kiến", "app.whiteboard.toolbar.tools": "Công cụ", "app.whiteboard.toolbar.tools.hand": "Pan", "app.whiteboard.toolbar.tools.pencil": "Bút chì", @@ -647,7 +626,6 @@ "app.externalVideo.autoPlayWarning": "Phát video để báºt đồng bá»™ hóa phương tiện", "app.network.connection.effective.slow": "Có vấn đỠvá» kết nối", "app.network.connection.effective.slow.help": "Thông tin thêm", - "app.externalVideo.noteLabel": "Lưu ý: Các video được chia sẻ sẽ không xuất hiện trong bản recording. Cho phép: YouTube, Vimeo, Instructure Media, Twitch, Daily Motion.", "app.actionsBar.actionsDropdown.shareExternalVideo": "Chia sẻ 1 video ở ngoà i", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Ngừng chia sẻ video", "app.iOSWarning.label": "Vui lòng nâng cấp lên IOS 12.2 hoặc cao hÆ¡n", diff --git a/bigbluebutton-html5/private/locales/zh_CN.json b/bigbluebutton-html5/private/locales/zh_CN.json index d503fa7210b9d28157ae04844f4722255af97490..8840f4fdddbc45c42555586d6242dee8958e117a 100644 --- a/bigbluebutton-html5/private/locales/zh_CN.json +++ b/bigbluebutton-html5/private/locales/zh_CN.json @@ -18,6 +18,7 @@ "app.chat.dropdown.save": "ä¿å˜", "app.chat.label": "èŠå¤©", "app.chat.offline": "离线", + "app.chat.pollResult": "投票结果", "app.chat.emptyLogLabel": "èŠå¤©æ—¥å¿—是空的", "app.chat.clearPublicChatMessage": "公共èŠå¤©åކå²è®°å½•已被主æŒäººæ¸…空", "app.chat.multi.typing": "多个用户æ£åœ¨è¾“å…¥...", @@ -30,15 +31,15 @@ "app.captions.menu.ariaStartDesc": "打开å—å¹•ç¼–è¾‘å™¨å¹¶å…³é—æ¨¡å¼", "app.captions.menu.select": "选择å¯ç”¨è¯è¨€", "app.captions.menu.ariaSelect": "å—幕è¯è¨€", - "app.captions.menu.subtitle": "请为会议ä¸çš„éšè—å—幕选择è¯è¨€å’Œæ ·å¼ã€‚", - "app.captions.menu.title": "éšè—å¼å—幕", + "app.captions.menu.subtitle": "请为会议ä¸çš„å—幕选择è¯è¨€å’Œæ ·å¼ã€‚", + "app.captions.menu.title": "å—幕", "app.captions.menu.fontSize": "大å°", "app.captions.menu.fontColor": "文本颜色", "app.captions.menu.fontFamily": "å—体", "app.captions.menu.backgroundColor": "背景色", "app.captions.menu.previewLabel": "预览", "app.captions.menu.cancelLabel": "å–æ¶ˆ", - "app.captions.pad.hide": "鮿Œ¡éšè—å¼å—幕", + "app.captions.pad.hide": "éšè—å—幕", "app.captions.pad.tip": "按Esc键定ä½åˆ°ç¼–辑器工具æ ", "app.captions.pad.ownership": "接管", "app.captions.pad.ownershipTooltip": "您将被指定为 {0} å—幕的所有者", @@ -50,10 +51,10 @@ "app.note.title": "分享笔记", "app.note.label": "笔记", "app.note.hideNoteLabel": "éšè—ç¬”è®°é¢æ¿", + "app.note.tipLabel": "按Esc键定ä½åˆ°ç¼–辑器工具æ ", "app.user.activityCheck": "用户活动检查", "app.user.activityCheck.label": "确认用户是å¦ä»ç„¶ä¼šè®®ä¸ï¼ˆ{0})", "app.user.activityCheck.check": "检查", - "app.note.tipLabel": "按Esc键定ä½åˆ°ç¼–辑器工具æ ", "app.userList.usersTitle": "用户", "app.userList.participantsTitle": "å‚会者", "app.userList.messagesTitle": "消æ¯", @@ -95,6 +96,8 @@ "app.userList.userOptions.unmuteAllDesc": "å–æ¶ˆä¼šè®®é™éŸ³", "app.userList.userOptions.lockViewersLabel": "é”定观众", "app.userList.userOptions.lockViewersDesc": "é”定æ¤ä¼šè®®å‚与者特定功能 ", + "app.userList.userOptions.connectionStatusLabel": "连接状æ€", + "app.userList.userOptions.connectionStatusDesc": "查看用户的连接状æ€", "app.userList.userOptions.disableCam": "å·²ç¦ç”¨è§‚众的摄åƒå¤´", "app.userList.userOptions.disableMic": "å·²ç¦ç”¨è§‚众的麦克风", "app.userList.userOptions.disablePrivChat": "å·²ç¦ç”¨ç§äººèŠå¤©", @@ -126,10 +129,13 @@ "app.meeting.meetingTimeRemaining": "会议时间剩余:{0} ", "app.meeting.meetingTimeHasEnded": "ç»“æŸæ—¶é—´åˆ°ã€‚会议å³å°†ç»“æŸ ", "app.meeting.endedMessage": "您将被跳转回首页", - "app.meeting.alertMeetingEndsUnderOneMinute": "会议将在一分钟之内结æŸã€‚ ", - "app.meeting.alertBreakoutEndsUnderOneMinute": "分组讨论将在一分钟之内结æŸã€‚", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "会议一分钟åŽå°±è¦ç»“æŸäº†ã€‚", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "会议 {0} 分钟åŽå°±è¦ç»“æŸäº†ã€‚", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "分组讨论 {0} 分钟åŽå°±è¦ç»“æŸäº†ã€‚", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "分组讨论一分钟åŽå°±è¦ç»“æŸäº†ã€‚", "app.presentation.hide": "éšè—演示", "app.presentation.notificationLabel": "当剿¼”示文稿", + "app.presentation.downloadLabel": "下载", "app.presentation.slideContent": "å¹»ç¯ç‰‡å†…容 ", "app.presentation.startSlideContent": "å¹»ç¯ç‰‡å†…容起始", "app.presentation.endSlideContent": "å¹»ç¯ç‰‡å†…å®¹ç»“æŸ ", @@ -174,6 +180,7 @@ "app.presentationUploder.rejectedError": "选定的文件(s)已被拒ç»ã€‚请检查文件类型(s)。", "app.presentationUploder.upload.progress": "ä¸Šä¼ ä¸({0}%)", "app.presentationUploder.upload.413": "文件太大,请分æˆå¤šä¸ªæ–‡ä»¶ã€‚", + "app.presentationUploder.genericError": "哎哟,出错了", "app.presentationUploder.upload.408": "请求上载令牌超时。", "app.presentationUploder.upload.404": "404:ä¸Šä¼ ä»¤ç‰Œæ— æ•ˆ", "app.presentationUploder.upload.401": "请求演示文稿上载令牌失败。", @@ -195,6 +202,7 @@ "app.presentationUploder.tableHeading.filename": "文件å", "app.presentationUploder.tableHeading.options": "选项", "app.presentationUploder.tableHeading.status": "状æ€", + "app.presentationUploder.clearErrors": "清除错误", "app.poll.pollPaneTitle": "投票", "app.poll.quickPollTitle": "快速投票", "app.poll.hidePollDesc": "éšè—投票èœå• ", @@ -267,7 +275,7 @@ "app.leaveConfirmation.confirmLabel": "退出", "app.leaveConfirmation.confirmDesc": "您将从会议退出", "app.endMeeting.title": "结æŸä¼šè®®", - "app.endMeeting.description": "确定è¦ç»“æŸä¼šè®®å—?将所有人踢出会议室!", + "app.endMeeting.description": "真的è¦ç»“æŸä¼šè®®å—(所有用户都将æ–开连接)?", "app.endMeeting.yesLabel": "是", "app.endMeeting.noLabel": "å¦", "app.about.title": "关于", @@ -288,10 +296,6 @@ "app.screenshare.screenShareLabel" : "分享å±å¹•", "app.submenu.application.applicationSectionTitle": "应用", "app.submenu.application.animationsLabel": "动画", - "app.submenu.application.audioAlertLabel": "èŠå¤©éŸ³é¢‘æé†’ ", - "app.submenu.application.pushAlertLabel": "èŠå¤©å¼¹çª—æé†’", - "app.submenu.application.userJoinAudioAlertLabel": "ç”¨æˆ·åŠ å…¥çš„éŸ³é¢‘é€šçŸ¥", - "app.submenu.application.userJoinPushAlertLabel": "ç”¨æˆ·åŠ å…¥çš„å¼¹å‡ºé€šçŸ¥", "app.submenu.application.fontSizeControlLabel": "å—å·", "app.submenu.application.increaseFontBtnLabel": "增大界é¢å—å·", "app.submenu.application.decreaseFontBtnLabel": "å‡å°ç•Œé¢å—å·", @@ -299,6 +303,10 @@ "app.submenu.application.languageLabel": "界é¢è¯è¨€", "app.submenu.application.languageOptionLabel": "选择è¯è¨€", "app.submenu.application.noLocaleOptionLabel": "没有å¯ç”¨çš„è¯è¨€", + "app.submenu.notification.SectionTitle": "通知", + "app.submenu.notification.audioAlertLabel": "音频æé†’", + "app.submenu.notification.pushAlertLabel": "弹窗æé†’", + "app.submenu.notification.messagesLabel": "èŠå¤©æ¶ˆæ¯", "app.submenu.audio.micSourceLabel": "麦克风æº", "app.submenu.audio.speakerSourceLabel": "扬声器æº", "app.submenu.audio.streamVolumeLabel": "您音频æµçš„音é‡", @@ -340,9 +348,9 @@ "app.actionsBar.actionsDropdown.pollBtnLabel": "å‘起投票", "app.actionsBar.actionsDropdown.pollBtnDesc": "æ‰“å¼€æŠ•ç¥¨é¢æ¿", "app.actionsBar.actionsDropdown.saveUserNames": "ä¿å˜ç”¨æˆ·å", - "app.actionsBar.actionsDropdown.createBreakoutRoom": "创建分组会议室", - "app.actionsBar.actionsDropdown.createBreakoutRoomDesc": "为当å‰ä¼šè®®åˆ›å»ºåˆ†ç»„会议室", - "app.actionsBar.actionsDropdown.captionsLabel": "编写éšè—å¼å—幕", + "app.actionsBar.actionsDropdown.createBreakoutRoom": "创建分组讨论会议室", + "app.actionsBar.actionsDropdown.createBreakoutRoomDesc": "为当å‰ä¼šè®®åˆ›å»ºåˆ†ç»„讨论会议室", + "app.actionsBar.actionsDropdown.captionsLabel": "编写å—幕", "app.actionsBar.actionsDropdown.captionsDesc": "切æ¢å—幕颿¿", "app.actionsBar.actionsDropdown.takePresenter": "我当演示者", "app.actionsBar.actionsDropdown.takePresenterDesc": "设定自己为新的演示者", @@ -368,8 +376,8 @@ "app.actionsBar.emojiMenu.thumbsDownLabel": "æ‹ç –", "app.actionsBar.emojiMenu.thumbsDownDesc": "更改您的状æ€ä¸ºæ‹ç –", "app.actionsBar.currentStatusDesc": "当å‰çжæ€{0}", - "app.actionsBar.captions.start": "开始查看éšè—å¼å—幕", - "app.actionsBar.captions.stop": "åœæ¢æŸ¥çœ‹éšè—å¼å—幕", + "app.actionsBar.captions.start": "开始查看å—幕", + "app.actionsBar.captions.stop": "åœæ¢æŸ¥çœ‹å—幕", "app.audioNotification.audioFailedError1001": "WebSocket æ–开了(error 1001)", "app.audioNotification.audioFailedError1002": "æ— æ³•å»ºç«‹ WebSocket 连接 (error 1002)", "app.audioNotification.audioFailedError1003": "䏿”¯æŒçš„æµè§ˆå™¨ç‰ˆæœ¬(error 1003)", @@ -386,14 +394,14 @@ "app.audioNotification.mediaFailedMessage": "getUserMicMedia å¤±è´¥ï¼Œå› ä¸ºåªå…è®¸å®‰å…¨çš„æ¥æº", "app.audioNotification.closeLabel": "å…³é—", "app.audioNotificaion.reconnectingAsListenOnly": "观众的麦克风被é”定,您现在仅能è†å¬ã€‚", - "app.breakoutJoinConfirmation.title": "åŠ å…¥åˆ†ç»„ä¼šè®®å®¤", + "app.breakoutJoinConfirmation.title": "åŠ å…¥åˆ†ç»„è®¨è®ºä¼šè®®å®¤", "app.breakoutJoinConfirmation.message": "æ‚¨æƒ³åŠ å…¥å—?", - "app.breakoutJoinConfirmation.confirmDesc": "å°†æ‚¨åŠ å…¥åˆ†ç»„ä¼šè®®å®¤", + "app.breakoutJoinConfirmation.confirmDesc": "å°†æ‚¨åŠ å…¥åˆ†ç»„è®¨è®ºä¼šè®®å®¤", "app.breakoutJoinConfirmation.dismissLabel": "å–æ¶ˆ", - "app.breakoutJoinConfirmation.dismissDesc": "å…³é—å¹¶æ‹’ç»åŠ å…¥åˆ†ç»„ä¼šè®®å®¤", - "app.breakoutJoinConfirmation.freeJoinMessage": "é€‰æ‹©å¹¶åŠ å…¥åˆ†ç»„ä¼šè®®å®¤", - "app.breakoutTimeRemainingMessage": "分组讨论剩余时间:{0}", - "app.breakoutWillCloseMessage": "分组讨论结æŸã€‚分组会议室将被关é—。", + "app.breakoutJoinConfirmation.dismissDesc": "å…³é—å¹¶æ‹’ç»åŠ å…¥åˆ†ç»„è®¨è®ºä¼šè®®å®¤", + "app.breakoutJoinConfirmation.freeJoinMessage": "é€‰æ‹©å¹¶åŠ å…¥åˆ†ç»„è®¨è®ºä¼šè®®å®¤", + "app.breakoutTimeRemainingMessage": "分组讨论会议室剩余时间:{0}", + "app.breakoutWillCloseMessage": "时间结æŸäº†ã€‚分组讨论会议室马上就è¦å…³é—。", "app.calculatingBreakoutTimeRemaining": "计算剩余时间...", "app.audioModal.ariaTitle": "åŠ å…¥éŸ³é¢‘æ¨¡å¼", "app.audioModal.microphoneLabel": "麦克风", @@ -486,8 +494,10 @@ "app.userList.guest.pendingGuestUsers": "{0}ä½è®¿å®¢ç”¨æˆ·ç‰å¾…ä¸", "app.userList.guest.pendingGuestAlert": "å·²ç»åŠ å…¥åˆ°ä¼šè®®ï¼Œå¹¶ä¸”æ£åœ¨ç‰å¾…您的批准。", "app.userList.guest.rememberChoice": "è®°ä½é€‰æ‹©", + "app.userList.guest.acceptLabel": "åŒæ„", + "app.userList.guest.denyLabel": "æ‹’ç»", "app.user-info.title": "目录查找", - "app.toast.breakoutRoomEnded": "分组讨论结æŸã€‚è¯·é‡æ–°åŠ å…¥éŸ³é¢‘äº¤æµã€‚", + "app.toast.breakoutRoomEnded": "分组讨论会议已结æŸã€‚è¯·é‡æ–°åŠ å…¥éŸ³é¢‘äº¤æµã€‚", "app.toast.chat.public": "新的公共èŠå¤©æ¶ˆæ¯", "app.toast.chat.private": "æ–°çš„ç§äººèŠå¤©æ¶ˆæ¯", "app.toast.chat.system": "系统", @@ -500,6 +510,7 @@ "app.notification.recordingPaused": "æ¤ä¼šè®®ä¸å†è¢«å½•制", "app.notification.recordingAriaLabel": "录制时间", "app.notification.userJoinPushAlert": "{0} åŠ å…¥äº†ä¼šè®®", + "app.submenu.notification.raiseHandLabel": "举手", "app.shortcut-help.title": "键盘快æ·é”®", "app.shortcut-help.accessKeyNotAvailable": "键盘快æ·é”®ä¸å¯ç”¨", "app.shortcut-help.comboLabel": "组åˆé”®", @@ -533,6 +544,9 @@ "app.lock-viewers.button.cancel": "å–æ¶ˆ", "app.lock-viewers.locked": "å·²é”定", "app.lock-viewers.unlocked": "未é”定", + "app.connection-status.title": "连接状æ€", + "app.connection-status.more": "更多", + "app.connection-status.offline": "离线", "app.recording.startTitle": "开始录制", "app.recording.stopTitle": "æš‚åœå½•制", "app.recording.resumeTitle": "æ¢å¤å½•制", @@ -540,10 +554,17 @@ "app.recording.stopDescription": "ç¡®å®šè¦æš‚åœå½•制å—ï¼Ÿå†æ¬¡ç‚¹â€œå½•åˆ¶â€æŒ‰é’®å¯ä»¥ç»§ç»å½•制。", "app.videoPreview.cameraLabel": "æ‘„åƒå¤´", "app.videoPreview.profileLabel": "è´¨é‡", + "app.videoPreview.quality.low": "低", + "app.videoPreview.quality.medium": "ä¸", + "app.videoPreview.quality.high": "高", + "app.videoPreview.quality.hd": "高清晰度", "app.videoPreview.cancelLabel": "å–æ¶ˆ", "app.videoPreview.closeLabel": "å…³é—", "app.videoPreview.findingWebcamsLabel": "æ£åœ¨æŸ¥æ‰¾æ‘„åƒå¤´", "app.videoPreview.startSharingLabel": "开始分享", + "app.videoPreview.stopSharingLabel": "åœæ¢åˆ†äº«", + "app.videoPreview.stopSharingAllLabel": "åœæ¢æ‰€æœ‰", + "app.videoPreview.sharedCameraLabel": "æ¤æ‘„åƒå¤´å·²è¢«åˆ†äº«", "app.videoPreview.webcamOptionLabel": "选择摄åƒå¤´", "app.videoPreview.webcamPreviewLabel": "æ‘„åƒå¤´é¢„览", "app.videoPreview.webcamSettingsTitle": "æ‘„åƒå¤´è®¾ç½®", @@ -573,19 +594,6 @@ "app.video.videoMenuDesc": "打开视频下拉èœå•", "app.video.chromeExtensionError": "您必须安装", "app.video.chromeExtensionErrorLink": "这个Chromeæµè§ˆå™¨æ‰©å±•æ’ä»¶", - "app.video.stats.title": "连接状æ€", - "app.video.stats.packetsReceived": "收到的数æ®åŒ…", - "app.video.stats.packetsSent": "å‘é€çš„æ•°æ®åŒ…", - "app.video.stats.packetsLost": "丢失的数æ®åŒ…", - "app.video.stats.bitrate": "比特率", - "app.video.stats.lostPercentage": "总丢包率", - "app.video.stats.lostRecentPercentage": "最近丢包率", - "app.video.stats.dimensions": "è§„æ ¼", - "app.video.stats.codec": "ç¼–ç è§£ç 器", - "app.video.stats.decodeDelay": "è§£ç 延迟", - "app.video.stats.rtt": "往返时间", - "app.video.stats.encodeUsagePercent": "ç¼–ç usage", - "app.video.stats.currentDelay": "当å‰å»¶è¿Ÿ", "app.fullscreenButton.label": "免屿˜¾ç¤º{0}", "app.deskshare.iceConnectionStateError": "分线å±å¹•时连接失败 (ICE error 1108)", "app.sfu.mediaServerConnectionError2000": "æ— æ³•è¿žæŽ¥åˆ°åª’ä½“æœåС噍 (error 2000)", @@ -598,7 +606,6 @@ "app.sfu.invalidSdp2202":"客户端生æˆäº†æ— 效的媒体请求 (SDP error 2202)", "app.sfu.noAvailableCodec2203": "æœåŠ¡å™¨æ‰¾ä¸åˆ°åˆé€‚的编解ç 器(error 2203)", "app.meeting.endNotification.ok.label": "确认", - "app.whiteboard.annotations.poll": "投票结果已公布", "app.whiteboard.toolbar.tools": "工具", "app.whiteboard.toolbar.tools.hand": "移动", "app.whiteboard.toolbar.tools.pencil": "铅笔", @@ -639,7 +646,7 @@ "app.videoDock.webcamUnfocusDesc": "å–æ¶ˆå¯¹ç„¦é€‰ä¸çš„æ‘„åƒå¤´", "app.videoDock.autoplayBlockedDesc": "æˆ‘ä»¬éœ€è¦æ‚¨çš„è®¸å¯æ‰èƒ½å‘您显示其他用户的摄åƒå¤´ã€‚", "app.videoDock.autoplayAllowLabel": "查看摄åƒå¤´", - "app.invitation.title": "分组讨论邀请", + "app.invitation.title": "分组讨论会议室邀请", "app.invitation.confirm": "邀请", "app.createBreakoutRoom.title": "分组讨论会议室", "app.createBreakoutRoom.ariaTitle": "éšè—分组讨论会议室", @@ -658,16 +665,16 @@ "app.createBreakoutRoom.numberOfRooms": "会议室数é‡", "app.createBreakoutRoom.durationInMinutes": "æŒç»æ—¶é—´ï¼ˆåˆ†é’Ÿï¼‰", "app.createBreakoutRoom.randomlyAssign": "éšæœºæŒ‡æ´¾", - "app.createBreakoutRoom.endAllBreakouts": "ç»“æŸæ‰€æœ‰åˆ†ç»„讨论", + "app.createBreakoutRoom.endAllBreakouts": "ç»“æŸæ‰€æœ‰åˆ†ç»„讨论会议室", "app.createBreakoutRoom.roomName": "{0}(会议室-{1})", "app.createBreakoutRoom.doneLabel": "完æˆ", "app.createBreakoutRoom.nextLabel": "下一æ¥", - "app.createBreakoutRoom.minusRoomTime": "将临时会议室时间å‡å°‘到", - "app.createBreakoutRoom.addRoomTime": "å°†ä¸´æ—¶ä¼šè®®å®¤æ—¶é—´å¢žåŠ åˆ°", + "app.createBreakoutRoom.minusRoomTime": "将分组讨论会议时间å‡å°‘到", + "app.createBreakoutRoom.addRoomTime": "å°†åˆ†ç»„è®¨è®ºä¼šè®®æ—¶é—´å¢žåŠ åˆ°", "app.createBreakoutRoom.addParticipantLabel": "+ æ·»åŠ å‚会人", - "app.createBreakoutRoom.freeJoin": "å…è®¸äººå‘˜é€‰æ‹©å¹¶åŠ å…¥åˆ†ç»„è®¨è®º", - "app.createBreakoutRoom.leastOneWarnBreakout": "您必须至少指派一ä½äººå‘˜åˆ°æ¯ä¸€ä¸ªåˆ†ç»„讨论", - "app.createBreakoutRoom.modalDesc": "æç¤ºï¼šæ‚¨å¯ä»¥æ‹–放用户å以将其分é…给特定的分组会议室。", + "app.createBreakoutRoom.freeJoin": "å…è®¸ç”¨æˆ·é€‰æ‹©å¹¶åŠ å…¥åˆ†ç»„è®¨è®ºä¼šè®®", + "app.createBreakoutRoom.leastOneWarnBreakout": "您必须至少指派一个用户到分组讨论会议室", + "app.createBreakoutRoom.modalDesc": "æç¤ºï¼šæ‚¨å¯ä»¥æ‹–放用户å以将其分é…给特定的分组讨论会议室。", "app.createBreakoutRoom.roomTime": "{0} 分钟", "app.createBreakoutRoom.numberOfRoomsError": "ä¼šè®®å®¤æ•°é‡æ— 效。", "app.externalVideo.start": "分享一个新视频", @@ -679,7 +686,6 @@ "app.externalVideo.autoPlayWarning": "æ’æ”¾è§†é¢‘以å¯ç”¨åª’ä½“åŒæ¥", "app.network.connection.effective.slow": "我们注æ„到了连接问题。", "app.network.connection.effective.slow.help": "更多信æ¯", - "app.externalVideo.noteLabel": "注æ„:共享的外部视频ä¸ä¼šå‡ºçŽ°åœ¨å½•åƒä¸ã€‚支æŒYouTubeã€Vimeoã€Instructure Mediaã€Twitchå’ŒDaily Motionçš„URL。", "app.actionsBar.actionsDropdown.shareExternalVideo": "分享外部视频", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "åœæ¢åˆ†äº«å¤–部视频", "app.iOSWarning.label": "请å‡çº§è‡³iOS 12.2或更高版本", diff --git a/bigbluebutton-html5/private/locales/zh_TW.json b/bigbluebutton-html5/private/locales/zh_TW.json index 52afebc12c44b88e4050ccfcffa11dd269bc1674..5b8c33047d6c3950b1279f75b3b585e0e7d85566 100644 --- a/bigbluebutton-html5/private/locales/zh_TW.json +++ b/bigbluebutton-html5/private/locales/zh_TW.json @@ -18,6 +18,7 @@ "app.chat.dropdown.save": "儲å˜", "app.chat.label": "èŠå¤©", "app.chat.offline": "下線", + "app.chat.pollResult": "æŠ•ç¥¨çµæžœ", "app.chat.emptyLogLabel": "空的èŠå¤©ç´€éŒ„", "app.chat.clearPublicChatMessage": "公開èŠå¤©ç´€éŒ„已被主æŒäººæ¸…空了", "app.chat.multi.typing": "多個使用者都在輸入", @@ -50,10 +51,10 @@ "app.note.title": "共享ç†è¨˜", "app.note.label": "ç†è¨˜", "app.note.hideNoteLabel": "éš±è—ç†è¨˜", + "app.note.tipLabel": "壓Escéµèšç„¦ç·¨è¼¯å™¨å·¥å…·åˆ—", "app.user.activityCheck": "用戶活動檢查", "app.user.activityCheck.label": "檢查用戶是å¦ä»åœ¨æœƒè°ä¸ ({0})", "app.user.activityCheck.check": "檢查", - "app.note.tipLabel": "壓Escéµèšç„¦ç·¨è¼¯å™¨å·¥å…·åˆ—", "app.userList.usersTitle": "用戶", "app.userList.participantsTitle": "åƒèˆ‡è€…", "app.userList.messagesTitle": "訊æ¯", @@ -74,6 +75,7 @@ "app.userList.menu.clearStatus.label": "清除狀態", "app.userList.menu.removeUser.label": "移除用戶", "app.userList.menu.removeConfirmation.label": "刪除使用者({0})", + "app.userlist.menu.removeConfirmation.desc": "阻æ¢è©²ç”¨æˆ¶é‡æ–°åŠ å…¥æœƒè©±ã€‚", "app.userList.menu.muteUserAudio.label": "用戶éœéŸ³", "app.userList.menu.unmuteUserAudio.label": "å–æ¶ˆç”¨æˆ¶éœéŸ³", "app.userList.userAriaLabel": "{0}{1}{2}狀態{3}", @@ -94,6 +96,8 @@ "app.userList.userOptions.unmuteAllDesc": "解除會è°å®¤éœéŸ³", "app.userList.userOptions.lockViewersLabel": "鎖定è½çœ¾", "app.userList.userOptions.lockViewersDesc": "鎖定è½çœ¾åœ¨æ¤æœƒè°ä¸çš„特定功能", + "app.userList.userOptions.connectionStatusLabel": "連線狀態", + "app.userList.userOptions.connectionStatusDesc": "檢視使用者連線狀態", "app.userList.userOptions.disableCam": "å·²åœç”¨èˆ‡æœƒè€…的視訊", "app.userList.userOptions.disableMic": "å·²åœç”¨èˆ‡æœƒè€…的麥克風", "app.userList.userOptions.disablePrivChat": "å·²åœç”¨ç§äººèŠå¤©", @@ -114,6 +118,7 @@ "app.media.screenshare.start": "ç•«é¢åˆ†äº«å·²é–‹å§‹", "app.media.screenshare.end": "ç•«é¢åˆ†äº«å·²çµæŸ", "app.media.screenshare.unavailable": "ç•«é¢åˆ†äº«ä¸èƒ½ç”¨", + "app.media.screenshare.notSupported": "這個ç€è¦½å™¨ä¸æ”¯æ´èž¢å¹•共享。", "app.media.screenshare.autoplayBlockedDesc": "æˆ‘å€‘éœ€è¦æ‚¨çš„è¨±å¯æ‰èƒ½å‘æ‚¨é¡¯ç¤ºç°¡å ±è€…ç•«é¢ã€‚", "app.media.screenshare.autoplayAllowLabel": "查看分享畫é¢", "app.screenshare.notAllowed": "錯誤: 未授與畫é¢å˜å–權é™", @@ -124,10 +129,13 @@ "app.meeting.meetingTimeRemaining": "å‰©é¤˜æœƒè°æ™‚é–“: {0}", "app.meeting.meetingTimeHasEnded": "æ™‚é–“çµæŸï¼Œå¾ˆå¿«æœƒè°å³å°‡é—œé–‰ã€‚", "app.meeting.endedMessage": "您很快將被帶回首é ", - "app.meeting.alertMeetingEndsUnderOneMinute": "會è°å°‡åœ¨ä¸€åˆ†é˜å…§çµæŸ", - "app.meeting.alertBreakoutEndsUnderOneMinute": "分組討論將在一分é˜ä¹‹å…§çµæŸ", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "會è°å°‡åœ¨ä¸€åˆ†é˜å…§çµæŸã€‚", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "會è°å°‡é—œé–‰åœ¨ {0} 分é˜å¾Œ.", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "在 {0} 分é˜å…§ï¼Œåˆ†çµ„會è°å°±æœƒçµæŸ", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "一分é˜å…§ï¼Œåˆ†çµ„會è°å°±æœƒçµæŸ", "app.presentation.hide": "éš±è—ç°¡å ±", "app.presentation.notificationLabel": "ç›®å‰ç°¡å ±", + "app.presentation.downloadLabel": "下載", "app.presentation.slideContent": "投影片內容", "app.presentation.startSlideContent": "投影片開始", "app.presentation.endSlideContent": "æŠ•å½±ç‰‡çµæŸ", @@ -172,6 +180,7 @@ "app.presentationUploder.rejectedError": "æ‰€é¸æª”案(複數檔)已被退回,請檢查(其它)æª”æ¡ˆæ ¼å¼ã€‚", "app.presentationUploder.upload.progress": "ä¸Šå‚³ä¸ ({0}%)", "app.presentationUploder.upload.413": "檔案太大了,請分æˆå¤šå€‹æª”案。", + "app.presentationUploder.genericError": "哎呀,發生å•題...", "app.presentationUploder.upload.408": "è¦æ±‚上傳token逾時。", "app.presentationUploder.upload.404": "404: 無效的上傳Token", "app.presentationUploder.upload.401": "è¦ç”±æ¼”講稿上傳Token失敗。", @@ -193,6 +202,13 @@ "app.presentationUploder.tableHeading.filename": "檔å", "app.presentationUploder.tableHeading.options": "é¸é …", "app.presentationUploder.tableHeading.status": "狀態", + "app.presentationUploder.uploading": "上傳ä¸{0}{1}", + "app.presentationUploder.uploadStatus": "{0} 分之 {1} 上傳完æˆ", + "app.presentationUploder.completed": "{0} 上傳完æˆ", + "app.presentationUploder.item" : "é …", + "app.presentationUploder.itemPlural" : "é …ç›®", + "app.presentationUploder.clearErrors": "清除錯誤符", + "app.presentationUploder.clearErrorsDesc": "æ¸…é™¤ä¸Šå‚³å¤±æ•—çš„ç°¡å ±", "app.poll.pollPaneTitle": "投票", "app.poll.quickPollTitle": "快速投票", "app.poll.hidePollDesc": "éš±è—æŠ•ç¥¨é¢ç‰ˆé¸å–®", @@ -238,6 +254,7 @@ "app.connectingMessage": "é€£ç·šä¸ ...", "app.waitingMessage": "連線終斷了。åšè©¦é‡æ–°å»ºç«‹é€£ç·š {0} ç§’", "app.retryNow": "ç«‹å³é‡è©¦", + "app.muteWarning.label": "é»žé¸ {0} å–æ¶ˆéœéŸ³", "app.navBar.settingsDropdown.optionsLabel": "é¸é …", "app.navBar.settingsDropdown.fullscreenLabel": "進入全螢幕", "app.navBar.settingsDropdown.settingsLabel": "è¨å®š", @@ -265,7 +282,7 @@ "app.leaveConfirmation.confirmLabel": "離開", "app.leaveConfirmation.confirmDesc": "將您登出會è°", "app.endMeeting.title": "çµæŸæœƒè°", - "app.endMeeting.description": "æ‚¨ç¢ºå®šå°‡çµæŸæœƒè°å—Žï¼Ÿ", + "app.endMeeting.description": "您確定è¦çµæŸæœƒè°ï¼ˆæ‰€æœ‰ç”¨æˆ¶éƒ½å°‡æ–·é–‹é€£æŽ¥ï¼‰å—Žï¼Ÿ", "app.endMeeting.yesLabel": "是", "app.endMeeting.noLabel": "å¦", "app.about.title": "關於", @@ -286,10 +303,6 @@ "app.screenshare.screenShareLabel" : "ç•«é¢åˆ†äº«", "app.submenu.application.applicationSectionTitle": "應用程å¼", "app.submenu.application.animationsLabel": "å‹•ç•«", - "app.submenu.application.audioAlertLabel": "èŠå¤©éŸ³è¨Šæé†’", - "app.submenu.application.pushAlertLabel": "èŠå¤©å½ˆå‡ºæé†’", - "app.submenu.application.userJoinAudioAlertLabel": "ä½¿ç”¨è€…åŠ å…¥éŸ³è¨Šæé†’", - "app.submenu.application.userJoinPushAlertLabel": "ä½¿ç”¨è€…åŠ å…¥å½ˆå‡ºæé†’", "app.submenu.application.fontSizeControlLabel": "å—型大å°", "app.submenu.application.increaseFontBtnLabel": "æé«˜æ‡‰ç”¨ç¨‹å¼å—é«”", "app.submenu.application.decreaseFontBtnLabel": "é™ä½Žæ‡‰ç”¨ç¨‹å¼å—é«”", @@ -297,6 +310,12 @@ "app.submenu.application.languageLabel": "應用程å¼èªžè¨€", "app.submenu.application.languageOptionLabel": "鏿“‡èªžè¨€", "app.submenu.application.noLocaleOptionLabel": "沒有å¯ç”¨çš„語言", + "app.submenu.notification.SectionTitle": "通知", + "app.submenu.notification.Desc": "定義如何以åŠå°‡æ”¶åˆ°ä»€éº¼é€šçŸ¥ã€‚", + "app.submenu.notification.audioAlertLabel": "è²éŸ³æé†’", + "app.submenu.notification.pushAlertLabel": "彈出æé†’", + "app.submenu.notification.messagesLabel": "å°è©±è¨Šæ¯", + "app.submenu.notification.userJoinLabel": "ä½¿ç”¨è€…åŠ å…¥", "app.submenu.audio.micSourceLabel": "麥克風來æº", "app.submenu.audio.speakerSourceLabel": "æšè²å™¨ä¾†æº", "app.submenu.audio.streamVolumeLabel": "您的串æµéŸ³é‡", @@ -320,6 +339,10 @@ "app.settings.dataSavingTab.screenShare": "啟用桌é¢åˆ†äº«", "app.settings.dataSavingTab.description": "調整當å‰é¡¯ç¤ºåŽ»ç¯€çœæ‚¨çš„é »å¯¬", "app.settings.save-notification.label": "已儲å˜è¨å®š", + "app.statusNotifier.lowerHands": "放下", + "app.statusNotifier.raisedHandsTitle": "舉手", + "app.statusNotifier.raisedHandDesc": "{0} 人舉起他們的手", + "app.statusNotifier.and": "並且", "app.switch.onLabel": "開啟", "app.switch.offLabel": "關閉", "app.talkingIndicator.ariaMuteDesc" : "éœéŸ³é¸å–使用戶", @@ -484,6 +507,8 @@ "app.userList.guest.pendingGuestUsers": "{0} ä½è¨ªå®¢ç‰å¾…ä¸", "app.userList.guest.pendingGuestAlert": "å·²åŠ å…¥æœƒè«‡ä¸¦ä¸”ç‰å¾…您的批淮。", "app.userList.guest.rememberChoice": "記ä½é¸æ“‡", + "app.userList.guest.acceptLabel": "åŒæ„", + "app.userList.guest.denyLabel": "拒絕", "app.user-info.title": "目錄查找", "app.toast.breakoutRoomEnded": "分組會è°å®¤çµæŸï¼Œè«‹é‡æ–°åŠ å…¥éŸ³è¨Šã€‚", "app.toast.chat.public": "新公開èŠå¤©è¨Šæ¯", @@ -498,6 +523,7 @@ "app.notification.recordingPaused": "æ¤æœƒè«‡ä¸å†è¢«å´éŒ„", "app.notification.recordingAriaLabel": "å´éŒ„時間", "app.notification.userJoinPushAlert": "{0} åŠ å…¥äº†æœƒè«‡", + "app.submenu.notification.raiseHandLabel": "舉手", "app.shortcut-help.title": "éµç›¤ç†±éµ", "app.shortcut-help.accessKeyNotAvailable": "熱éµä¸å¯ç”¨", "app.shortcut-help.comboLabel": "組åˆéµ", @@ -531,6 +557,12 @@ "app.lock-viewers.button.cancel": "å–æ¶ˆ", "app.lock-viewers.locked": "已鎖定", "app.lock-viewers.unlocked": "鎖定已解除", + "app.connection-status.ariaTitle": "連線狀態模組", + "app.connection-status.title": "連線狀態\n ", + "app.connection-status.description": "檢視使用者連線狀態", + "app.connection-status.empty": "ç¾åœ¨æ²’有任何連線å•é¡Œå›žå ±", + "app.connection-status.more": "更多", + "app.connection-status.offline": "下線", "app.recording.startTitle": "開始錄製", "app.recording.stopTitle": "æš«åœéŒ„製", "app.recording.resumeTitle": "æ¢å¾©éŒ„製", @@ -538,10 +570,17 @@ "app.recording.stopDescription": "æ‚¨ç¢ºå®šè¦æš«åœéŒ„製嗎? 您å¯ä»¥å†æ¬¡é¸æ“‡éŒ„製按鈕繼續。", "app.videoPreview.cameraLabel": "æ”影機", "app.videoPreview.profileLabel": "å“質", + "app.videoPreview.quality.low": "低", + "app.videoPreview.quality.medium": "ä¸", + "app.videoPreview.quality.high": "高", + "app.videoPreview.quality.hd": "高解æžåº¦", "app.videoPreview.cancelLabel": "å–æ¶ˆ", "app.videoPreview.closeLabel": "關閉", "app.videoPreview.findingWebcamsLabel": "尋找網路æ”å½±è£ç½®", "app.videoPreview.startSharingLabel": "開始分享", + "app.videoPreview.stopSharingLabel": "åœæ¢åˆ†äº«", + "app.videoPreview.stopSharingAllLabel": "åœæ¢å…¨éƒ¨", + "app.videoPreview.sharedCameraLabel": "這個æ”影機已分享", "app.videoPreview.webcamOptionLabel": "鏿“‡ç¶²è·¯æ”影機", "app.videoPreview.webcamPreviewLabel": "網路æ”影機é 覽", "app.videoPreview.webcamSettingsTitle": "網路æ”影機è¨å®š", @@ -571,19 +610,8 @@ "app.video.videoMenuDesc": "打開視訊下拉é¸å–®", "app.video.chromeExtensionError": "您必需安è£", "app.video.chromeExtensionErrorLink": "這個Chromeæ“´å……", - "app.video.stats.title": "連線狀態", - "app.video.stats.packetsReceived": "收到的å°åŒ…", - "app.video.stats.packetsSent": "é€å‡ºçš„å°åŒ…", - "app.video.stats.packetsLost": "éºå¤±çš„å°åŒ…", - "app.video.stats.bitrate": "ä½å…ƒçއ", - "app.video.stats.lostPercentage": "éºå¤±å…¨éƒ¨å…¨ç™¾åˆ†", - "app.video.stats.lostRecentPercentage": "最近百分比éºå¤±", - "app.video.stats.dimensions": "è¦æ ¼", - "app.video.stats.codec": "編解碼器", - "app.video.stats.decodeDelay": "解碼延é²", - "app.video.stats.rtt": "往返時間", - "app.video.stats.encodeUsagePercent": "編碼使用", - "app.video.stats.currentDelay": "ç›®å‰å»¶é²", + "app.video.pagination.prevPage": "看上一部影片", + "app.video.pagination.nextPage": "看下一部影片", "app.fullscreenButton.label": "全螢幕顯示 {0}", "app.deskshare.iceConnectionStateError": "分享螢幕時連線失敗(ICE 錯誤 1108)", "app.sfu.mediaServerConnectionError2000": "無法連接媒體伺æœå™¨(錯誤 2000)", @@ -597,7 +625,8 @@ "app.sfu.noAvailableCodec2203": "伺æœå™¨ç„¡æ³•找到åˆé©çš„編解碼器 (錯誤 2203)", "app.meeting.endNotification.ok.label": "確定", "app.whiteboard.annotations.poll": "å…¬ä½ˆæŠ•ç¥¨èª¿æ•´çµæžœ", - "app.whiteboard.toolbar.tools": "工具", + "app.whiteboard.annotations.pollResult": "æŠ•ç¥¨çµæžœ", + "app.whiteboard.toolbar.tools": "工具組", "app.whiteboard.toolbar.tools.hand": "移動", "app.whiteboard.toolbar.tools.pencil": "ç†", "app.whiteboard.toolbar.tools.rectangle": "長方型", @@ -665,7 +694,7 @@ "app.createBreakoutRoom.addParticipantLabel": "+新增與會者", "app.createBreakoutRoom.freeJoin": "å…è¨±èˆ‡æœƒè€…é¸æ“‡ä¸¦åŠ å…¥åˆ†çµ„è¨Žè«–æœƒè°å®¤", "app.createBreakoutRoom.leastOneWarnBreakout": "您必需指派一ä½äººå“¡åˆ°æ¯å€‹åˆ†çµ„討論會è°å®¤", - "app.createBreakoutRoom.modalDesc": "æç¤ºï¼šæ‚¨å¯ä»¥æ‹–放用戶å稱,以將其分é…給特定的分組討論室。\n", + "app.createBreakoutRoom.modalDesc": "æç¤ºï¼šæ‚¨å¯ä»¥æ‹–放用戶å稱,以將其分é…給特定的分組討論室。", "app.createBreakoutRoom.roomTime": "{0} 分é˜", "app.createBreakoutRoom.numberOfRoomsError": "ä¸å¯ç”¨çš„分組會è°å®¤æ•¸é‡", "app.externalVideo.start": "分享一個影片", @@ -677,7 +706,7 @@ "app.externalVideo.autoPlayWarning": "æ’æ”¾å½±ç‰‡ä»¥å•Ÿç”¨åª’é«”åŒæ¥", "app.network.connection.effective.slow": "我們查覺到連接å•題。", "app.network.connection.effective.slow.help": "更多資訊", - "app.externalVideo.noteLabel": "注æ„ï¼šå…±äº«çš„å¤–éƒ¨å½±ç‰‡å°‡ä¸æœƒå‡ºç¾åœ¨éŒ„å½±ä¸ã€‚ 支æ´YouTube,Vimeo,Instructure Media,Twitchå’ŒDaily Motion URL。", + "app.externalVideo.noteLabel": "註: åˆ†äº«å¤–éƒ¨å½±ç‰‡ä¸æœƒåœ¨å´éŒ„ä¸é¡¯ç¤ºã€‚\nYouTubeã€Vimeoã€Instructure Mediaã€Twitchã€DailymotionåŠè§£åª’體檔案網å€( 例如: https://example.com/xy.mp4 ) 有支æ´ã€‚", "app.actionsBar.actionsDropdown.shareExternalVideo": "分享一個外部影片", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "åœæ¢åˆ†äº«å¤–部影片。", "app.iOSWarning.label": "è«‹å‡ç´š iOS 12.2 或更高", diff --git a/bigbluebutton-html5/public/resources/images/avatar.png b/bigbluebutton-html5/public/resources/images/avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..2e9f8151f4083aece663009ae40d9271104dbb41 Binary files /dev/null and b/bigbluebutton-html5/public/resources/images/avatar.png differ diff --git a/bigbluebutton-html5/tests/puppeteer/all.test.js b/bigbluebutton-html5/tests/puppeteer/all.test.js new file mode 100644 index 0000000000000000000000000000000000000000..bf27d988bc4c38750cb5fd6ad70c821a3c7870b4 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/all.test.js @@ -0,0 +1,27 @@ +const audioTest = require('./audio.obj'); +const chatTest = require('./chat.obj'); +const breakoutTest = require('./breakout.obj'); +const customParametersTest = require('./customparameters.obj'); +const notificationsTest = require('./notifications.obj'); +const presentationTest = require('./presentation.obj'); +const screenShareTest = require('./screenshare.obj'); +const sharedNotesTest = require('./sharednotes.obj'); +const userTest = require('./user.obj'); +const virtualizedListTest = require('./virtualizedlist.obj'); +const webcamTest = require('./webcam.obj'); +const whiteboardTest = require('./whiteboard.obj'); + +process.setMaxListeners(Infinity); + +describe('Audio', audioTest); +describe('Chat', chatTest); +describe('Breakout', breakoutTest); +describe('Custom Parameters', customParametersTest); +describe('Notifications', notificationsTest); +describe('Presentation', presentationTest); +describe('Screen Share', screenShareTest); +describe('Shared Notes ', sharedNotesTest); +describe('User', userTest); +describe('Virtualized List', virtualizedListTest); +describe('Webcam', webcamTest); +describe('Whiteboard', whiteboardTest); diff --git a/bigbluebutton-html5/tests/puppeteer/audio.obj.js b/bigbluebutton-html5/tests/puppeteer/audio.obj.js new file mode 100644 index 0000000000000000000000000000000000000000..9470f7afbd0ec10b9332b3f9d925479234f02360 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/audio.obj.js @@ -0,0 +1,38 @@ +const Audio = require('./audio/audio'); +const Page = require('./core/page'); + +const audioTest = () => { + beforeEach(() => { + jest.setTimeout(30000); + }); + + test('Join audio with Listen Only', async () => { + const test = new Audio(); + let response; + try { + await test.init(Page.getArgsWithAudio()); + response = await test.test(); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); + + test('Join audio with Microphone', async () => { + const test = new Audio(); + let response; + try { + await test.init(Page.getArgsWithAudio()); + response = await test.microphone(); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); +}; + +module.exports = exports = audioTest; diff --git a/bigbluebutton-html5/tests/puppeteer/audio.test.js b/bigbluebutton-html5/tests/puppeteer/audio.test.js index fa0e3c0a991526f704016af6535eba14db0df888..7316567675dac4c6c67bbe383575b211b208a802 100644 --- a/bigbluebutton-html5/tests/puppeteer/audio.test.js +++ b/bigbluebutton-html5/tests/puppeteer/audio.test.js @@ -1,36 +1,3 @@ -const Audio = require('./audio/audio'); -const Page = require('./core/page'); +const audioTest = require('./audio.obj'); -describe('Audio', () => { - beforeEach(() => { - jest.setTimeout(30000); - }); - - test('Join audio with Listen Only', async () => { - const test = new Audio(); - let response; - try { - await test.init(Page.getArgsWithAudio()); - response = await test.test(); - } catch (e) { - console.log(e); - } finally { - await test.close(); - } - expect(response).toBe(true); - }); - - test('Join audio with Microphone', async () => { - const test = new Audio(); - let response; - try { - await test.init(Page.getArgsWithAudio()); - response = await test.microphone(); - } catch (e) { - console.log(e); - } finally { - await test.close(); - } - expect(response).toBe(true); - }); -}); +describe('Audio', audioTest); diff --git a/bigbluebutton-html5/tests/puppeteer/breakout.obj.js b/bigbluebutton-html5/tests/puppeteer/breakout.obj.js new file mode 100644 index 0000000000000000000000000000000000000000..4e1bb169323d7b6f8e2bb0f99ba27aca369c57a4 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/breakout.obj.js @@ -0,0 +1,99 @@ +const Join = require('./breakout/join'); +const Create = require('./breakout/create'); +const Page = require('./core/page'); + +const breakoutTest = () => { + beforeEach(() => { + jest.setTimeout(150000); + }); + + // // Create Breakout Room + // test('Create Breakout room', async () => { + // const test = new Create(); + // let response; + // try { + // const testName = 'createBreakoutrooms'; + // await test.init(undefined); + // await test.create(testName); + // response = await test.testCreatedBreakout(testName); + // } catch (e) { + // console.log(e); + // } finally { + // await test.close(); + // } + // expect(response).toBe(true); + // }); + + // // Join Breakout Room + // test('Join Breakout room', async () => { + // const test = new Join(); + // let response; + // try { + // const testName = 'joinBreakoutroomsWithoutFeatures'; + // await test.init(undefined); + // await test.create(testName); + // await test.join(testName); + // response = await test.testJoined(testName); + // } catch (e) { + // console.log(e); + // } finally { + // await test.close(); + // } + // expect(response).toBe(true); + // }); + + // // Join Breakout Room with Video + // test('Join Breakout room with Video', async () => { + // const test = new Join(); + // let response; + // try { + // const testName = 'joinBreakoutroomsWithVideo'; + // await test.init(undefined); + // await test.create(testName); + // await test.join(testName); + // response = await test.testJoined(testName); + // } catch (e) { + // console.log(e); + // } finally { + // await test.close(); + // } + // expect(response).toBe(true); + // }); + + // // Join Breakout Room and start Screen Share + // test('Join Breakout room and share screen', async () => { + // const test = new Join(); + // let response; + // try { + // const testName = 'joinBreakoutroomsAndShareScreen'; + // await test.init(undefined); + // await test.create(testName); + // await test.join(testName); + // response = await test.testJoined(testName); + // } catch (e) { + // console.log(e); + // } finally { + // await test.close(); + // } + // expect(response).toBe(true); + // }); + + // Join Breakout Room with Audio + test('Join Breakout room with Audio', async () => { + const test = new Join(); + let response; + try { + const testName = 'joinBreakoutroomsWithAudio'; + await test.init(undefined); + await test.create(testName); + await test.join(testName); + response = await test.testJoined(testName); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); +}; +module.exports = exports = breakoutTest; diff --git a/bigbluebutton-html5/tests/puppeteer/breakout.test.js b/bigbluebutton-html5/tests/puppeteer/breakout.test.js index 4e335bea854da31c38984bed6a5d49065b020135..e8106562cd81338e0b984139cfa25e8560ea46e4 100644 --- a/bigbluebutton-html5/tests/puppeteer/breakout.test.js +++ b/bigbluebutton-html5/tests/puppeteer/breakout.test.js @@ -1,98 +1,3 @@ -const Join = require('./breakout/join'); -const Create = require('./breakout/create'); -const Page = require('./core/page'); +const breakoutTest = require('./breakout.obj'); -describe('Breakoutrooms', () => { - beforeEach(() => { - jest.setTimeout(150000); - }); - - // Create Breakout Room - test('Create Breakout room', async () => { - const test = new Create(); - let response; - try { - const testName = 'createBreakoutrooms'; - await test.init(undefined); - await test.create(testName); - response = await test.testCreatedBreakout(testName); - } catch (e) { - console.log(e); - } finally { - await test.close(); - } - expect(response).toBe(true); - }); - - // Join Breakout Room - test('Join Breakout room', async () => { - const test = new Join(); - let response; - try { - const testName = 'joinBreakoutroomsWithoutFeatures'; - await test.init(undefined); - await test.create(testName); - await test.join(testName); - response = await test.testJoined(testName); - } catch (e) { - console.log(e); - } finally { - await test.close(); - } - expect(response).toBe(true); - }); - - // Join Breakout Room with Video - test('Join Breakout room with Video', async () => { - const test = new Join(); - let response; - try { - const testName = 'joinBreakoutroomsWithVideo'; - await test.init(undefined); - await test.create(testName); - await test.join(testName); - response = await test.testJoined(testName); - } catch (e) { - console.log(e); - } finally { - await test.close(); - } - expect(response).toBe(true); - }); - - // Join Breakout Room and start Screen Share - test('Join Breakout room and share screen', async () => { - const test = new Join(); - let response; - try { - const testName = 'joinBreakoutroomsAndShareScreen'; - await test.init(undefined); - await test.create(testName); - await test.join(testName); - response = await test.testJoined(testName); - } catch (e) { - console.log(e); - } finally { - await test.close(); - } - expect(response).toBe(true); - }); - - // Join Breakout Room with Audio - test('Join Breakout room with Audio', async () => { - const test = new Join(); - let response; - try { - const testName = 'joinBreakoutroomsWithAudio'; - await test.init(undefined); - await test.create(testName); - await test.join(testName); - response = await test.testJoined(testName); - } catch (e) { - console.log(e); - } finally { - await test.close(); - } - expect(response).toBe(true); - }); -}); +describe('Breakout', breakoutTest); diff --git a/bigbluebutton-html5/tests/puppeteer/breakout/create.js b/bigbluebutton-html5/tests/puppeteer/breakout/create.js index 5282281478a96fbdf1b4dd6bad2f034cece3e076..402901fb33352d24f2f6ab0477c29ff0f825c62d 100644 --- a/bigbluebutton-html5/tests/puppeteer/breakout/create.js +++ b/bigbluebutton-html5/tests/puppeteer/breakout/create.js @@ -25,44 +25,62 @@ class Create { // Create Breakoutrooms async create(testName) { - await this.page1.screenshot(`${testName}`, `01-page01-initialized-${testName}`); - await this.page2.screenshot(`${testName}`, `01-page02-initialized-${testName}`); - this.page1.logger('page01 initialized'); - this.page2.logger('page02 initialized'); + await this.page1.closeAudioModal(); + await this.page2.closeAudioModal(); + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.page1.screenshot(`${testName}`, `01-page01-initialized-${testName}`); + await this.page2.screenshot(`${testName}`, `01-page02-initialized-${testName}`); + } await this.page1.page.evaluate(util.clickTestElement, be.manageUsers); await this.page1.page.evaluate(util.clickTestElement, be.createBreakoutRooms); - this.page1.logger('page01 breakout rooms menu loaded'); - await this.page1.screenshot(`${testName}`, `02-page01-creating-breakoutrooms-${testName}`); + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.page1.screenshot(`${testName}`, `02-page01-creating-breakoutrooms-${testName}`); + } await this.page1.waitForSelector(be.randomlyAssign); await this.page1.page.evaluate(util.clickTestElement, be.randomlyAssign); - this.page1.logger('page01 randomly assigned users'); - await this.page1.screenshot(`${testName}`, `03-page01-randomly-assign-user-${testName}`); + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.page1.screenshot(`${testName}`, `03-page01-randomly-assign-user-${testName}`); + } await this.page1.waitForSelector(be.modalConfirmButton); await this.page1.page.evaluate(util.clickTestElement, be.modalConfirmButton); - this.page1.logger('page01 breakout rooms creation confirmed'); - await this.page1.screenshot(`${testName}`, `04-page01-confirm-breakoutrooms-creation-${testName}`); + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.page1.screenshot(`${testName}`, `04-page01-confirm-breakoutrooms-creation-${testName}`); + } await this.page2.waitForSelector(be.modalConfirmButton); await this.page2.page.evaluate(util.clickTestElement, be.modalConfirmButton); - this.page2.logger('page02 breakout rooms join confirmed'); - await this.page2.screenshot(`${testName}`, `02-page02-accept-invite-breakoutrooms-${testName}`); - + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.page2.screenshot(`${testName}`, `02-page02-accept-invite-breakoutrooms-${testName}`); + } + await this.page2.page.bringToFront(); + await this.page2.waitForSelector(be.breakoutRoomsItem); + await this.page2.waitForSelector(be.chatButton); + await this.page2.click(be.chatButton); + await this.page2.click(be.breakoutRoomsItem); + await this.page2.waitForSelector(be.alreadyConnected); const page2 = await this.page2.browser.pages(); - this.page2.logger('before closing audio modal'); - await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/03-breakout-page02-before-closing-audio-modal.png`) }); - await this.page2.waitForBreakoutElement('button[aria-label="Close Join audio modal"]', 2); - await this.page2.clickBreakoutElement('button[aria-label="Close Join audio modal"]', 2); - await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/04-breakout-page02-after-closing-audio-modal.png`) }); - this.page2.logger('audio modal closed'); + await page2[2].bringToFront(); + if (process.env.GENERATE_EVIDENCES === 'true') { + await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/03-breakout-page02-before-closing-audio-modal.png`) }); + } + await page2[2].waitForSelector('button[aria-label="Close Join audio modal"]'); + await page2[2].click('button[aria-label="Close Join audio modal"]'); + if (process.env.GENERATE_EVIDENCES === 'true') { + await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/04-breakout-page02-after-closing-audio-modal.png`) }); + } } // Check if Breakoutrooms have been created async testCreatedBreakout(testName) { const resp = await this.page1.page.evaluate(() => document.querySelectorAll('div[data-test="breakoutRoomsItem"]').length !== 0); if (resp === true) { - await this.page1.screenshot(`${testName}`, `05-page01-success-${testName}`); + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.page1.screenshot(`${testName}`, `05-page01-success-${testName}`); + } return true; } - await this.page1.screenshot(`${testName}`, `05-page01-fail-${testName}`); + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.page1.screenshot(`${testName}`, `05-page01-fail-${testName}`); + } return false; } @@ -73,13 +91,23 @@ class Create { await this.page3.closeAudioModal(); await this.page3.waitForSelector(be.breakoutRoomsButton); await this.page3.click(be.breakoutRoomsButton, true); + + await this.page3.waitForSelector(be.breakoutRoomsItem); + await this.page3.waitForSelector(be.chatButton); + await this.page3.click(be.chatButton); + await this.page3.click(be.breakoutRoomsItem); + + await this.page3.waitForSelector(be.joinRoom1); await this.page3.click(be.joinRoom1, true); await this.page3.waitForSelector(be.alreadyConnected); const page3 = await this.page3.browser.pages(); - await page3[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/00-breakout-page03-user-joined-no-mic-before-check-${testName}.png`) }); + if (process.env.GENERATE_EVIDENCES === 'true') { + await page3[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/00-breakout-page03-user-joined-no-mic-before-check-${testName}.png`) }); + } + await page3[2].bringToFront(); await page3[2].waitForSelector(ae.microphone); await page3[2].click(ae.microphone); await page3[2].waitForSelector(ae.connectingStatus); @@ -87,9 +115,9 @@ class Create { await page3[2].click(ae.audioAudible); await page3[2].waitForSelector(e.whiteboard); - await page3[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/00-breakout-page03-user-joined-with-mic-before-check-${testName}.png`) }); - - this.page3.logger('joined breakout with audio'); + if (process.env.GENERATE_EVIDENCES === 'true') { + await page3[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/00-breakout-page03-user-joined-with-mic-before-check-${testName}.png`) }); + } } else if (testName === 'joinBreakoutroomsWithVideo') { await this.page3.init(Page.getArgsWithVideo(), this.page1.meetingId, { ...params, fullName: 'Moderator3' }, undefined); await this.page3.closeAudioModal(); @@ -101,7 +129,10 @@ class Create { const page3 = await this.page3.browser.pages(); - await page3[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/00-breakout-page03-user-joined-no-webcam-before-check-${testName}.png`) }); + if (process.env.GENERATE_EVIDENCES === 'true') { + await page3[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/00-breakout-page03-user-joined-no-webcam-before-check-${testName}.png`) }); + } + await page3[2].bringToFront(); await page3[2].waitForSelector(e.audioDialog); await page3[2].waitForSelector(e.closeAudio); await page3[2].click(e.closeAudio); @@ -111,9 +142,9 @@ class Create { await page3[2].click(we.videoPreview); await page3[2].waitForSelector(we.startSharingWebcam); await page3[2].click(we.startSharingWebcam); - await page3[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/00-breakout-page03-user-joined-with-webcam-before-check-${testName}.png`) }); - - this.page3.logger('joined breakout with video'); + if (process.env.GENERATE_EVIDENCES === 'true') { + await page3[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/00-breakout-page03-user-joined-with-webcam-before-check-${testName}.png`) }); + } } else if (testName === 'joinBreakoutroomsAndShareScreen') { await this.page3.init(Page.getArgs(), this.page1.meetingId, { ...params, fullName: 'Moderator3' }, undefined); await this.page3.closeAudioModal(); @@ -124,6 +155,10 @@ class Create { await this.page3.waitForSelector(be.alreadyConnected); const page3 = await this.page3.browser.pages(); + if (process.env.GENERATE_EVIDENCES === 'true') { + await page3[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/00-breakout-page03-user-joined-with-screenshare-before-check-${testName}.png`) }); + } + await page3[2].bringToFront(); await page3[2].waitForSelector(e.audioDialog); await page3[2].waitForSelector(e.closeAudio); await page3[2].click(e.closeAudio); @@ -140,18 +175,12 @@ class Create { await page3[2].on('dialog', async (dialog) => { await dialog.accept(); }); - - this.page3.logger('joined breakout and started screen share'); + if (process.env.GENERATE_EVIDENCES === 'true') { + await page3[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/00-breakout-page03-user-joined-with-screenshare-after-check-${testName}.png`) }); + } } else { await this.page3.init(Page.getArgs(), this.page1.meetingId, { ...params, fullName: 'Moderator3' }, undefined); await this.page3.closeAudioModal(); - await this.page3.waitForSelector(be.breakoutRoomsButton); - await this.page3.click(be.breakoutRoomsButton, true); - await this.page3.waitForSelector(be.joinRoom1); - await this.page3.click(be.joinRoom1, true); - await this.page3.waitForSelector(be.alreadyConnected); - - this.page3.logger('joined breakout without use of any feature'); } } diff --git a/bigbluebutton-html5/tests/puppeteer/breakout/elements.js b/bigbluebutton-html5/tests/puppeteer/breakout/elements.js index 3d96da32e8e7c445ac74c3b90559dc64eee74c19..997d68b159920a60e10f785493b25dcfa2045249 100644 --- a/bigbluebutton-html5/tests/puppeteer/breakout/elements.js +++ b/bigbluebutton-html5/tests/puppeteer/breakout/elements.js @@ -10,3 +10,4 @@ exports.breakoutJoin = '[data-test="breakoutJoin"]'; exports.userJoined = 'div[aria-label^="Moderator3"]'; exports.breakoutRoomsButton = 'div[aria-label="Breakout Rooms"]'; exports.joinRoom1 = 'button[aria-label="Join room 1"]'; +exports.chatButton = 'div[data-test="chatButton"]'; diff --git a/bigbluebutton-html5/tests/puppeteer/breakout/join.js b/bigbluebutton-html5/tests/puppeteer/breakout/join.js index 39734d17b83fb220b4fd429c54f1cf610a06b54e..7428974b1cc81cb988b7dbd02c5564438b50faca 100644 --- a/bigbluebutton-html5/tests/puppeteer/breakout/join.js +++ b/bigbluebutton-html5/tests/puppeteer/breakout/join.js @@ -16,15 +16,7 @@ class Join extends Create { // Join Existing Breakoutrooms async join(testName) { - if (testName === 'joinBreakoutroomsWithAudio') { - await this.joinWithUser3(testName); - } else if (testName === 'joinBreakoutroomsWithVideo') { - await this.joinWithUser3(testName); - } else if (testName === 'joinBreakoutroomsAndShareScreen') { - await this.joinWithUser3(testName); - } else { - await this.joinWithUser3(testName); - } + await this.joinWithUser3(testName); } // Check if User Joined in Breakoutrooms @@ -35,14 +27,23 @@ class Join extends Create { this.page3.logger('logged in to breakout with audio'); const page2 = await this.page2.browser.pages(); - await page2[2].waitForSelector(pe.isTalking); - await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/05-breakout-page02-user-joined-with-audio-before-check-${testName}.png`) }); + + await page2[2].bringToFront(); + + // Talking indicator bar + await page2[2].waitForSelector('div[class^="isTalkingWrapper--"] > div[class^="speaking--"]'); + + if (process.env.GENERATE_EVIDENCES === 'true') { + await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/05-breakout-page02-user-joined-with-audio-before-check-${testName}.png`) }); + } this.page3.logger('before pages check'); const respTalkingIndicatorElement = await page2[2].evaluate(util.getTestElement, pe.isTalking); const resp = respTalkingIndicatorElement === true; - await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/06-breakout-page02-user-joined-with-audio-after-check-${testName}.png`) }); + if (process.env.GENERATE_EVIDENCES === 'true') { + await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/06-breakout-page02-user-joined-with-audio-after-check-${testName}.png`) }); + } this.page3.logger('after pages check'); return resp; } catch (e) { @@ -53,13 +54,17 @@ class Join extends Create { const page2 = await this.page2.browser.pages(); await page2[2].waitForSelector(we.videoContainer); - await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/05-breakout-page02-user-joined-with-webcam-success-${testName}.png`) }); + if (process.env.GENERATE_EVIDENCES === 'true') { + await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/05-breakout-page02-user-joined-with-webcam-success-${testName}.png`) }); + } this.page2.logger('before pages check'); const respWebcamElement = await page2[2].evaluate(util.getTestElement, we.videoContainer); const resp = respWebcamElement === true; - await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/06-breakout-page02-user-joined-webcam-before-check-${testName}.png`) }); + if (process.env.GENERATE_EVIDENCES === 'true') { + await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/06-breakout-page02-user-joined-webcam-before-check-${testName}.png`) }); + } this.page2.logger('after pages check'); return resp; } else if (testName === 'joinBreakoutroomsAndShareScreen') { @@ -67,24 +72,26 @@ class Join extends Create { const page2 = await this.page2.browser.pages(); await page2[2].waitForSelector(pe.screenShareVideo); - await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/05-breakout-page02-user-joined-webcam-after-check-${testName}.png`) }); + if (process.env.GENERATE_EVIDENCES === 'true') { + await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/05-breakout-page02-user-joined-webcam-after-check-${testName}.png`) }); + } this.page2.logger('before pages check'); const resp = await page2[2].evaluate(async () => { const screenshareContainerElement = await document.querySelectorAll('video[id="screenshareVideo"]').length !== 0; return screenshareContainerElement === true; }); - await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/06-breakout-page02-user-joined-with-webcam-success-${testName}.png`) }); + if (process.env.GENERATE_EVIDENCES === 'true') { + await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/06-breakout-page02-user-joined-with-webcam-success-${testName}.png`) }); + } this.page2.logger('after pages check'); return resp; } else { - const page2 = await this.page2.browser.pages(); - await page2[2].waitForSelector(e.userJoined); - await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/05-breakout-page02-user-joined-before-check-${testName}.png`) }); - const resp = await page2[2].evaluate(async () => { - const foundUserElement = await document.querySelectorAll('div[aria-label^="Moderator3"]').length !== 0; - return foundUserElement; - }); - await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/06-breakout-page02-user-joined-after-check-${testName}.png`) }); + await this.page3.page.bringToFront(); + await this.page3.waitForSelector(e.breakoutRoomsItem); + await this.page3.waitForSelector(e.chatButton); + await this.page3.click(e.chatButton); + await this.page3.click(e.breakoutRoomsItem); + const resp = await this.page3.page.evaluate(async () => await document.querySelectorAll('span[class^="alreadyConnected--"]') !== null); return resp; } } diff --git a/bigbluebutton-html5/tests/puppeteer/chat.obj.js b/bigbluebutton-html5/tests/puppeteer/chat.obj.js new file mode 100644 index 0000000000000000000000000000000000000000..5c6e213ccfbbbfc4a749645a032d936c7f507801 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/chat.obj.js @@ -0,0 +1,111 @@ +const Page = require('./core/page'); +const Send = require('./chat/send'); +const Clear = require('./chat/clear'); +const Copy = require('./chat/copy'); +const Save = require('./chat/save'); +const MultiUsers = require('./user/multiusers'); + +const chatTest = () => { + beforeEach(() => { + jest.setTimeout(30000); + }); + + test('Send message', async () => { + const test = new Send(); + let response; + try { + const testName = 'sendChat'; + await test.init(Page.getArgs()); + await test.closeAudioModal(); + response = await test.test(testName); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); + + test('Clear chat', async () => { + const test = new Clear(); + let response; + try { + const testName = 'clearChat'; + await test.init(Page.getArgs()); + await test.closeAudioModal(); + response = await test.test(testName); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); + + test('Copy chat', async () => { + const test = new Copy(); + let response; + try { + const testName = 'copyChat'; + await test.init(Page.getArgs()); + await test.closeAudioModal(); + response = await test.test(testName); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); + + test('Save chat', async () => { + const test = new Save(); + let response; + try { + const testName = 'saveChat'; + await test.init(Page.getArgs()); + await test.closeAudioModal(); + response = await test.test(testName); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); + + test('Send private chat to other User', async () => { + const test = new MultiUsers(); + let response; + try { + const testName = 'sendPrivateChat'; + await test.init(); + await test.page1.closeAudioModal(); + await test.page2.closeAudioModal(); + response = await test.multiUsersPrivateChat(testName); + } catch (e) { + console.log(e); + } finally { + await test.close(test.page1, test.page2); + } + expect(response).toBe(true); + }); + + test('Send public chat', async () => { + const test = new MultiUsers(); + let response; + try { + const testName = 'sendPublicChat'; + await test.init(); + await test.page1.closeAudioModal(); + await test.page2.closeAudioModal(); + response = await test.multiUsersPublicChat(testName); + } catch (e) { + console.log(e); + } finally { + await test.close(test.page1, test.page2); + } + expect(response).toBe(true); + }); +}; +module.exports = exports = chatTest; diff --git a/bigbluebutton-html5/tests/puppeteer/chat.test.js b/bigbluebutton-html5/tests/puppeteer/chat.test.js index 67360c2b9299ec548800f2a2720f10a89ebc836f..1fe00e4ad551a2b1020cc9cab7392551bb4550c4 100644 --- a/bigbluebutton-html5/tests/puppeteer/chat.test.js +++ b/bigbluebutton-html5/tests/puppeteer/chat.test.js @@ -1,104 +1,3 @@ -const Page = require('./core/page'); -const Send = require('./chat/send'); -const Clear = require('./chat/clear'); -const Copy = require('./chat/copy'); -const Save = require('./chat/save'); -const MultiUsers = require('./user/multiusers'); +const chatTest = require('./chat.obj'); -describe('Chat', () => { - beforeEach(() => { - jest.setTimeout(30000); - }); - - test('Send message', async () => { - const test = new Send(); - let response; - try { - await test.init(Page.getArgs()); - await test.closeAudioModal(); - response = await test.test(); - } catch (e) { - console.log(e); - } finally { - await test.close(); - } - expect(response).toBe(true); - }); - - test('Clear chat', async () => { - const test = new Clear(); - let response; - try { - await test.init(Page.getArgs()); - await test.closeAudioModal(); - response = await test.test(); - } catch (e) { - console.log(e); - } finally { - await test.close(); - } - expect(response).toBe(true); - }); - - test('Copy chat', async () => { - const test = new Copy(); - let response; - try { - await test.init(Page.getArgs()); - await test.closeAudioModal(); - response = await test.test(); - } catch (e) { - console.log(e); - } finally { - await test.close(); - } - expect(response).toBe(true); - }); - - test('Save chat', async () => { - const test = new Save(); - let response; - try { - await test.init(Page.getArgs()); - await test.closeAudioModal(); - response = await test.test(); - } catch (e) { - console.log(e); - } finally { - await test.close(); - } - expect(response).toBe(true); - }); - - test('Send private chat to other User', async () => { - const test = new MultiUsers(); - let response; - try { - await test.init(); - await test.page1.closeAudioModal(); - await test.page2.closeAudioModal(); - response = await test.multiUsersPrivateChat(); - } catch (e) { - console.log(e); - } finally { - await test.close(test.page1, test.page2); - } - expect(response).toBe(true); - }); - - test('Send public chat', async () => { - const test = new MultiUsers(); - let response; - try { - await test.init(); - await test.page1.closeAudioModal(); - await test.page2.closeAudioModal(); - response = await test.multiUsersPublicChat(); - } catch (e) { - console.log(e); - } finally { - await test.close(test.page1, test.page2); - } - expect(response).toBe(true); - }); -}); +describe('Chat', chatTest); diff --git a/bigbluebutton-html5/tests/puppeteer/chat/clear.js b/bigbluebutton-html5/tests/puppeteer/chat/clear.js index bf7f31837efbb0c786c275f7ceb4973e36cc3963..1639eff1285ab2cd66c643313343d33b670709cc 100644 --- a/bigbluebutton-html5/tests/puppeteer/chat/clear.js +++ b/bigbluebutton-html5/tests/puppeteer/chat/clear.js @@ -3,39 +3,44 @@ const Page = require('../core/page'); const e = require('./elements'); const util = require('./util'); +const { chatPushAlerts } = require('../notifications/elements'); class Clear extends Page { constructor() { super('chat-clear'); } - async test() { + async test(testName) { await util.openChat(this); - + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.screenshot(`${testName}`, `01-before-chat-message-send-[${testName}]`); + } // sending a message await this.type(e.chatBox, e.message); await this.click(e.sendButton); - await this.screenshot(true); - // const before = await util.getTestElements(this); + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.screenshot(`${testName}`, `02-after-chat-message-send-[${testName}]`); + } - // 1 message - const chat0 = await this.page.$$(`${e.chatUserMessage} ${e.chatMessageText}`); + const chat0 = await this.page.evaluate(() => document.querySelectorAll('[data-test="chatUserMessage"]').length !== 0); // clear await this.click(e.chatOptions); + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.screenshot(`${testName}`, `03-chat-options-clicked-[${testName}]`); + } await this.click(e.chatClear, true); - await this.screenshot(true); - - // const after = await util.getTestElements(this); - - // 1 message - const chat1 = await this.page.$$(`${e.chatUserMessage} ${e.chatMessageText}`); - - expect(await chat0[0].evaluate(n => n.innerText)).toBe(e.message); + await this.page.waitForFunction( + 'document.querySelector("body").innerText.includes("The public chat history was cleared by a moderator")', + ); + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.screenshot(`${testName}`, `04-chat-cleared-[${testName}]`); + } - const response = chat0.length === 1 && chat1.length === 0; + const chat1 = await this.page.evaluate(() => document.querySelectorAll('[data-test="chatUserMessage"]').length !== 0); + const response = chat0 === true && chat1 === false; return response; } } diff --git a/bigbluebutton-html5/tests/puppeteer/chat/copy.js b/bigbluebutton-html5/tests/puppeteer/chat/copy.js index 849b6030c79d1d3ac47e0b94c51fc7bf06a69ff2..8d77255c9b152c8aebdd8997ea5348907fd17d3c 100644 --- a/bigbluebutton-html5/tests/puppeteer/chat/copy.js +++ b/bigbluebutton-html5/tests/puppeteer/chat/copy.js @@ -10,20 +10,28 @@ class Copy extends Page { super('chat-copy'); } - async test() { + async test(testName) { await util.openChat(this); - + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.screenshot(`${testName}`, `01-before-sending-chat-message-[${testName}]`); + } // sending a message await this.type(e.chatBox, e.message); await this.click(e.sendButton); - await this.screenshot(true); - + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.screenshot(`${testName}`, `02-chat-message-sent-[${testName}]`); + } await this.click(e.chatOptions); + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.screenshot(`${testName}`, `03-chat-options-clicked-[${testName}]`); + } await this.click(e.chatCopy, true); - const copiedChat = clipboardy.readSync(); - - return copiedChat.includes(`User1 : ${e.message}`); + // enable access to browser context clipboard + const context = await this.browser.defaultBrowserContext(); + await context.overridePermissions(process.env.BBB_SERVER_URL, ['clipboard-read']); + const copiedText = await this.page.evaluate(async () => await navigator.clipboard.readText()); + return copiedText.includes(`User1 : ${e.message}`); } } diff --git a/bigbluebutton-html5/tests/puppeteer/chat/save.js b/bigbluebutton-html5/tests/puppeteer/chat/save.js index ee3e04f832a4e4e7b86376a0c56713d907aaa3bf..b55b19948744967ee2fac5e52256d7765d276d2b 100644 --- a/bigbluebutton-html5/tests/puppeteer/chat/save.js +++ b/bigbluebutton-html5/tests/puppeteer/chat/save.js @@ -9,10 +9,15 @@ class Save extends Page { super('chat-save'); } - async test() { + async test(testName) { await util.openChat(this); - + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.screenshot(`${testName}`, `01-before-chat-options-click-[${testName}]`); + } await this.click(e.chatOptions); + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.screenshot(`${testName}`, `02-chat-options-clicked-[${testName}]`); + } await this.click(e.chatSave, true); let clicked = ''; clicked = await this.page.addListener('click', () => document.addEventListener('click')); diff --git a/bigbluebutton-html5/tests/puppeteer/chat/send.js b/bigbluebutton-html5/tests/puppeteer/chat/send.js index dcc52609b9e7d324a0f47adf04a33ac48228eb78..52bb99bd6cf787d7e8be539b2ce4dc99a569f6e8 100644 --- a/bigbluebutton-html5/tests/puppeteer/chat/send.js +++ b/bigbluebutton-html5/tests/puppeteer/chat/send.js @@ -9,26 +9,26 @@ class Send extends Page { super('chat-send'); } - async test() { + async test(testName) { await util.openChat(this); - // const chat0 = await util.getTestElements(this); - // 0 messages const chat0 = await this.page.$$(`${e.chatUserMessage} ${e.chatMessageText}`); - + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.screenshot(`${testName}`, `01-before-chat-message-send-[${testName}]`); + } // send a message await this.type(e.chatBox, e.message); + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.screenshot(`${testName}`, `02-typing-chat-message-[${testName}]`); + } await this.click(e.sendButton); - await this.screenshot(true); - - // const chat1 = await util.getTestElements(this); + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.screenshot(`${testName}`, `03-after-chat-message-send-[${testName}]`); + } // 1 message const chat1 = await this.page.$$(`${e.chatUserMessage} ${e.chatMessageText}`); - - expect(await chat1[0].evaluate(n => n.innerText)).toBe(e.message); - const response = chat0.length === 0 && chat1.length === 1; return response; diff --git a/bigbluebutton-html5/tests/puppeteer/core/page.js b/bigbluebutton-html5/tests/puppeteer/core/page.js index 30d5ab2851c47c432d4b78883ad29de3d788063f..423a1416166b388f4d94694bb8caf31916ea6157 100644 --- a/bigbluebutton-html5/tests/puppeteer/core/page.js +++ b/bigbluebutton-html5/tests/puppeteer/core/page.js @@ -190,6 +190,34 @@ class Page { }; } + static getArgsWithAudioAndVideo() { + if (process.env.BROWSERLESS_ENABLED === 'true') { + const args = [ + '--no-sandbox', + '--use-fake-ui-for-media-stream', + '--use-fake-device-for-media-stream', + '--lang=en-US', + ]; + return { + headless: true, + args, + }; + } + const args = [ + '--no-sandbox', + '--use-fake-ui-for-media-stream', + '--use-fake-device-for-media-stream', + `--use-file-for-fake-audio-capture=${path.join(__dirname, '../media/audio.wav')}`, + `--use-file-for-fake-video-capture=${path.join(__dirname, '../media/video_rgb.y4m')}`, + '--allow-file-access', + '--lang=en-US', + ]; + return { + headless: true, + args, + }; + } + // Returns a Promise that resolves when an element does not exist/is removed from the DOM elementRemoved(element) { return this.page.waitFor(element => !document.querySelector(element), {}, element); diff --git a/bigbluebutton-html5/tests/puppeteer/customparameters.obj.js b/bigbluebutton-html5/tests/puppeteer/customparameters.obj.js new file mode 100644 index 0000000000000000000000000000000000000000..69ff973d4ed5b2aaa58a8fe0276b14e6541b84b2 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/customparameters.obj.js @@ -0,0 +1,433 @@ +const Page = require('./core/page'); +const CustomParameters = require('./customparameters/customparameters'); +const c = require('./customparameters/constants'); +const util = require('./customparameters/util'); + +const customParametersTest = () => { + beforeEach(() => { + jest.setTimeout(50000); + }); + + // This test spec sets the userdata-autoJoin parameter to false + // and checks that the users don't get audio modal on login + test('Auto join', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'autoJoin'; + page.logger('before ', testName); + response = await test.autoJoin(testName, Page.getArgs(), undefined, c.autoJoin); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.closePage(test.page1); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-listenOnlyMode parameter to false + // and checks that the users can't see or use listen Only mode + test('Listen Only Mode', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'listenOnlyMode'; + page.logger('before ', testName); + response = await test.listenOnlyMode(testName, Page.getArgsWithAudio(), undefined, c.listenOnlyMode); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.close(test.page1, test.page2); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-forceListenOnly parameter to false + // and checks that the Viewers can only use listen only mode + test('Force Listen Only', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'forceListenOnly'; + page.logger('before ', testName); + response = await test.forceListenOnly(testName, Page.getArgsWithAudio(), undefined, c.forceListenOnly); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.closePage(test.page2); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-skipCheck parameter to true + // and checks that the users automatically skip audio check when clicking on Microphone + test('Skip audio check', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'skipCheck'; + page.logger('before ', testName); + response = await test.skipCheck(testName, Page.getArgsWithAudio(), undefined, c.skipCheck); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.closePage(test.page1); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-clientTitle parameter to some value + // and checks that the meeting window name starts with that value + test('Client title', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'clientTitle'; + page.logger('before ', testName); + response = await test.clientTitle(testName, Page.getArgs(), undefined, c.clientTitle); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.closePage(test.page1); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-askForFeedbackOnLogout parameter to true + // and checks that the users automatically get asked for feedback on logout page + test('Ask For Feedback On Logout', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'askForFeedbackOnLogout'; + page.logger('before ', testName); + response = await test.askForFeedbackOnLogout(testName, Page.getArgs(), undefined, c.askForFeedbackOnLogout); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.closePage(test.page1); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-displayBrandingArea parameter to true and add a logo link + // and checks that the users see the logo displaying in the meeting + test('Display Branding Area', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'displayBrandingArea'; + page.logger('before ', testName); + const parameterWithLogo = `${c.displayBrandingArea}&${c.logo}`; + response = await test.displayBrandingArea(testName, Page.getArgs(), undefined, parameterWithLogo); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.closePage(test.page1); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-shortcuts parameter to one or a list of shortcuts parameters + // and checks that the users can use those shortcuts + test('Shortcuts', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'shortcuts'; + page.logger('before ', testName); + response = await test.shortcuts(testName, Page.getArgs(), undefined, encodeURI(c.shortcuts)); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.closePage(test.page1); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-enableScreensharing parameter to false + // and checks that the Moderator can not see the Screen sharing button + test('Enable Screensharing', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'enableScreensharing'; + page.logger('before ', testName); + response = await test.enableScreensharing(testName, Page.getArgs(), undefined, c.enableScreensharing); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.closePage(test.page1); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-enableVideo parameter to false + // and checks that the Moderator can not see the Webcam sharing button + test('Enable Webcam', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'enableVideo'; + page.logger('before ', testName); + response = await test.enableVideo(testName, Page.getArgsWithVideo(), undefined, c.enableVideo); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.closePage(test.page1); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-autoShareWebcam parameter to true + // and checks that the Moderator sees the Webcam Settings Modal automatically at his connection to meeting + test('Auto Share Webcam', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'autoShareWebcam'; + page.logger('before ', testName); + response = await test.autoShareWebcam(testName, Page.getArgsWithVideo(), undefined, c.autoShareWebcam); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.closePage(test.page1); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-multiUserPenOnly parameter to true + // and checks that at multi Users whiteboard other users can see only pencil as drawing tool + test('Multi Users Pen Only', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'multiUserPenOnly'; + page.logger('before ', testName); + response = await test.multiUserPenOnly(testName, Page.getArgs(), undefined, c.multiUserPenOnly); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.close(test.page1, test.page2); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-presenterTools parameter to an interval of parameters + // and checks that at multi Users whiteboard Presenter can see only the set tools from the interval + test('Presenter Tools', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'presenterTools'; + page.logger('before ', testName); + response = await test.presenterTools(testName, Page.getArgs(), undefined, encodeURI(c.presenterTools)); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.closePage(test.page1); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-multiUserTools parameter to an interval of parameters + // and checks that at multi Users whiteboard other users can see only the set tools from the interval + test('Multi Users Tools', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'multiUserTools'; + page.logger('before ', testName); + response = await test.multiUserTools(testName, Page.getArgs(), undefined, encodeURI(c.multiUserTools)); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.close(test.page1, test.page2); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-customStyle parameter to an interval of styles + // and checks that the meeting displays what was called in the styles interval + test('Custom Styles', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'customStyle'; + page.logger('before ', testName); + response = await test.customStyle(testName, Page.getArgs(), undefined, encodeURI(c.customStyle)); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.closePage(test.page1); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-customStyleUrl parameter to a styles URL + // and checks that the meeting displays what was called in the styles URL + test('Custom Styles URL', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'customStyleUrl'; + page.logger('before ', testName); + response = await test.customStyleUrl(testName, Page.getArgs(), undefined, encodeURI(c.customStyleUrl)); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.closePage(test.page1); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-autoSwapLayout parameter to true + // and checks that at any webcam share, the focus will be on the webcam, + // and the presentation gets minimized and the available shared webcam will replace the Presentation + test('Auto Swap Layout', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'autoSwapLayout'; + page.logger('before ', testName); + response = await test.autoSwapLayout(testName, Page.getArgs(), undefined, encodeURI(c.autoSwapLayout)); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.closePage(test.page1); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-hidePresentation parameter to true + // and checks that the Presentation is totally hidden, and its place will be displaying a message + test('Hide Presentation', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'hidePresentation'; + page.logger('before ', testName); + response = await test.hidePresentation(testName, Page.getArgs(), undefined, encodeURI(c.hidePresentation)); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.closePage(test.page1); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-bannerText parameter to some text + // and checks that the meeting has a banner bar containing the same text + test('Banner Text', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'bannerText'; + page.logger('before ', testName); + response = await test.bannerText(testName, Page.getArgs(), undefined, encodeURI(c.bannerText)); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.closePage(test.page1); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-bannerColor parameter to some hex color value + // and checks that the meeting has a banner bar containing that color in rgb(r, g, b) + test('Banner Color', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'bannerColor'; + page.logger('before ', testName); + const colorToRGB = util.hexToRgb(c.color); + response = await test.bannerColor(testName, Page.getArgs(), undefined, `${c.bannerColor}&${encodeURI(c.bannerText)}`, colorToRGB); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.closePage(test.page1); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-bbb_show_public_chat_on_login parameter to false + // and checks that the users don't see that box by default + test('Show Public Chat On Login', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'showPublicChatOnLogin'; + page.logger('before ', testName); + response = await test.showPublicChatOnLogin(testName, Page.getArgs(), undefined, `${c.showPublicChatOnLogin}`); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.closePage(test.page1); + } + expect(response).toBe(true); + }); + + // This test spec sets the userdata-bbb_force_restore_presentation_on_new_events parameter to true + // and checks that the viewers get the presentation restored forcefully when the Moderator zooms + // in/out the presentation or publishes a poll or adds an annotation + test('Force Restore Presentation On New Events', async () => { + const test = new CustomParameters(); + const page = new Page(); + let response; + try { + const testName = 'forceRestorePresentationOnNewEvents'; + page.logger('before ', testName); + response = await test.forceRestorePresentationOnNewEvents(testName, Page.getArgs(), undefined, `${c.forceRestorePresentationOnNewEvents}`); + page.logger('after ', testName); + } catch (e) { + page.logger(e); + } finally { + await test.close(test.page1, test.page2); + } + expect(response).toBe(true); + }); +}; +module.exports = exports = customParametersTest; diff --git a/bigbluebutton-html5/tests/puppeteer/customparameters.test.js b/bigbluebutton-html5/tests/puppeteer/customparameters.test.js index 292a8503852aaacc17794e5b03b0ab1cb3f48c14..3506eef2d2e76006203c4758c7d5256ed0b1ec4a 100644 --- a/bigbluebutton-html5/tests/puppeteer/customparameters.test.js +++ b/bigbluebutton-html5/tests/puppeteer/customparameters.test.js @@ -1,432 +1,3 @@ -const Page = require('./core/page'); -const CustomParameters = require('./customparameters/customparameters'); -const c = require('./customparameters/constants'); -const util = require('./customparameters/util'); +const customParametersTest = require('./customparameters.obj'); -describe('Custom parameters', () => { - beforeEach(() => { - jest.setTimeout(30000); - }); - - // This test spec sets the userdata-autoJoin parameter to false - // and checks that the users don't get audio modal on login - test('Auto join', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'autoJoin'; - response = await test.autoJoin(testName, Page.getArgs(), undefined, c.autoJoin); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.closePage(test.page1); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-listenOnlyMode parameter to false - // and checks that the users can't see or use listen Only mode - test('Listen Only Mode', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'listenOnlyMode'; - response = await test.listenOnlyMode(testName, Page.getArgsWithAudio(), undefined, c.listenOnlyMode); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.close(test.page1, test.page2); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-forceListenOnly parameter to false - // and checks that the Viewers can only use listen only mode - test('Force Listen Only', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'forceListenOnly'; - response = await test.forceListenOnly(testName, Page.getArgsWithAudio(), undefined, c.forceListenOnly); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.closePage(test.page2); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-skipCheck parameter to true - // and checks that the users automatically skip audio check when clicking on Microphone - test('Skip audio check', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'skipCheck'; - response = await test.skipCheck(testName, Page.getArgsWithAudio(), undefined, c.skipCheck); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.closePage(test.page1); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-clientTitle parameter to some value - // and checks that the meeting window name starts with that value - test('Client title', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'clientTitle'; - response = await test.clientTitle(testName, Page.getArgs(), undefined, c.clientTitle); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.closePage(test.page1); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-askForFeedbackOnLogout parameter to true - // and checks that the users automatically get asked for feedback on logout page - test('Ask For Feedback On Logout', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'askForFeedbackOnLogout'; - response = await test.askForFeedbackOnLogout(testName, Page.getArgs(), undefined, c.askForFeedbackOnLogout); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.closePage(test.page1); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-displayBrandingArea parameter to true and add a logo link - // and checks that the users see the logo displaying in the meeting - test('Display Branding Area', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'displayBrandingArea'; - const parameterWithLogo = `${c.displayBrandingArea}&${c.logo}`; - response = await test.displayBrandingArea(testName, Page.getArgs(), undefined, parameterWithLogo); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.closePage(test.page1); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-shortcuts parameter to one or a list of shortcuts parameters - // and checks that the users can use those shortcuts - test('Shortcuts', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'shortcuts'; - response = await test.shortcuts(testName, Page.getArgs(), undefined, encodeURI(c.shortcuts)); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.closePage(test.page1); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-enableScreensharing parameter to false - // and checks that the Moderator can not see the Screen sharing button - test('Enable Screensharing', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'enableScreensharing'; - response = await test.enableScreensharing(testName, Page.getArgs(), undefined, c.enableScreensharing); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.closePage(test.page1); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-enableVideo parameter to false - // and checks that the Moderator can not see the Webcam sharing button - test('Enable Webcam', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'enableVideo'; - response = await test.enableVideo(testName, Page.getArgsWithVideo(), undefined, c.enableVideo); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.closePage(test.page1); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-autoShareWebcam parameter to true - // and checks that the Moderator sees the Webcam Settings Modal automatically at his connection to meeting - test('Auto Share Webcam', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'autoShareWebcam'; - response = await test.autoShareWebcam(testName, Page.getArgsWithVideo(), undefined, c.autoShareWebcam); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.closePage(test.page1); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-multiUserPenOnly parameter to true - // and checks that at multi Users whiteboard other users can see only pencil as drawing tool - test('Multi Users Pen Only', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'multiUserPenOnly'; - response = await test.multiUserPenOnly(testName, Page.getArgs(), undefined, c.multiUserPenOnly); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.close(test.page1, test.page2); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-presenterTools parameter to an interval of parameters - // and checks that at multi Users whiteboard Presenter can see only the set tools from the interval - test('Presenter Tools', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'presenterTools'; - response = await test.presenterTools(testName, Page.getArgs(), undefined, encodeURI(c.presenterTools)); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.closePage(test.page1); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-multiUserTools parameter to an interval of parameters - // and checks that at multi Users whiteboard other users can see only the set tools from the interval - test('Multi Users Tools', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'multiUserTools'; - response = await test.multiUserTools(testName, Page.getArgs(), undefined, encodeURI(c.multiUserTools)); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.close(test.page1, test.page2); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-customStyle parameter to an interval of styles - // and checks that the meeting displays what was called in the styles interval - test('Custom Styles', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'customStyle'; - response = await test.customStyle(testName, Page.getArgs(), undefined, encodeURIComponent(c.customStyle)); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.closePage(test.page1); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-customStyleUrl parameter to a styles URL - // and checks that the meeting displays what was called in the styles URL - test('Custom Styles URL', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'customStyleUrl'; - response = await test.customStyleUrl(testName, Page.getArgs(), undefined, encodeURI(c.customStyleUrl)); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.closePage(test.page1); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-autoSwapLayout parameter to true - // and checks that at any webcam share, the focus will be on the webcam, - // and the presentation gets minimized and the available shared webcam will replace the Presentation - test('Auto Swap Layout', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'autoSwapLayout'; - response = await test.autoSwapLayout(testName, Page.getArgs(), undefined, encodeURI(c.autoSwapLayout)); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.closePage(test.page1); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-hidePresentation parameter to true - // and checks that the Presentation is totally hidden, and its place will be displaying a message - test('Hide Presentation', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'hidePresentation'; - response = await test.hidePresentation(testName, Page.getArgs(), undefined, encodeURI(c.hidePresentation)); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.closePage(test.page1); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-bannerText parameter to some text - // and checks that the meeting has a banner bar containing the same text - test('Banner Text', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'bannerText'; - response = await test.bannerText(testName, Page.getArgs(), undefined, c.bannerText); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.closePage(test.page1); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-bannerColor parameter to some hex color value - // and checks that the meeting has a banner bar containing that color in rgb(r, g, b) - test('Banner Color', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'bannerColor'; - const colorToRGB = util.hexToRgb(c.color); - response = await test.bannerColor(testName, Page.getArgs(), undefined, `${c.bannerColor}&${encodeURI(c.bannerText)}`, colorToRGB); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.closePage(test.page1); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-bbb_show_public_chat_on_login parameter to false - // and checks that the users don't see that box by default - test('Show Public Chat On Login', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'showPublicChatOnLogin'; - response = await test.showPublicChatOnLogin(testName, Page.getArgs(), undefined, `${c.showPublicChatOnLogin}`); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.closePage(test.page1); - } - expect(response).toBe(true); - }); - - // This test spec sets the userdata-bbb_force_restore_presentation_on_new_events parameter to true - // and checks that the viewers get the presentation restored forcefully when the Moderator zooms - // in/out the presentation or publishes a poll or adds an annotation - test('Force Restore Presentation On New Events', async () => { - const test = new CustomParameters(); - const page = new Page(); - let response; - try { - page.logger('before'); - const testName = 'forceRestorePresentationOnNewEvents'; - response = await test.forceRestorePresentationOnNewEvents(testName, Page.getArgs(), undefined, `${c.forceRestorePresentationOnNewEvents}`); - page.logger('after'); - } catch (e) { - page.logger(e); - } finally { - await test.close(test.page1, test.page2); - } - expect(response).toBe(true); - }); -}); +describe('Custom Parameters', customParametersTest); diff --git a/bigbluebutton-html5/tests/puppeteer/customparameters/constants.js b/bigbluebutton-html5/tests/puppeteer/customparameters/constants.js index b0bac2a433a96817f67e41cbd39cbd7f8a280fb3..cf2b88c19a81a55addb11ed69acbc768fde8bb16 100644 --- a/bigbluebutton-html5/tests/puppeteer/customparameters/constants.js +++ b/bigbluebutton-html5/tests/puppeteer/customparameters/constants.js @@ -15,8 +15,9 @@ exports.autoShareWebcam = 'userdata-bbb_auto_share_webcam=true'; exports.multiUserPenOnly = 'userdata-bbb_multi_user_pen_only=true'; exports.presenterTools = 'userdata-bbb_presenter_tools=["pencil", "hand"]'; exports.multiUserTools = 'userdata-bbb_multi_user_tools=["pencil", "hand"]'; -exports.customStyle = 'userdata-bbb_custom_style=:root{--loader-bg:#000;}.overlay--1aTlbi{background-color:#000!important;}body{background-color:#000!important;}.presentationTitle--1LT79g{display: none;}.navbar--Z2lHYbG{display:none;}.actionsbar--Z1ant6U{display:none;}'; -exports.customStyleUrl = 'userdata-bbb_custom_style_url=https://bbb22.bbbvm.imdt.com.br/styles.css'; +const cssCode = '.presentationTitle--1LT79g{display: none;}'; +exports.customStyle = `userdata-bbb_custom_style=${cssCode}`; +exports.customStyleUrl = 'userdata-bbb_custom_style_url=https://develop.bigbluebutton.org/css-test-file.css'; exports.autoSwapLayout = 'userdata-bbb_auto_swap_layout=true'; exports.hidePresentation = 'userdata-bbb_hide_presentation=true'; exports.outsideToggleSelfVoice = 'userdata-bbb_outside_toggle_self_voice=true'; @@ -24,5 +25,5 @@ exports.outsideToggleRecording = 'userdata-bbb_outside_toggle_recording=true'; exports.showPublicChatOnLogin = 'userdata-bbb_show_public_chat_on_login=false'; exports.forceRestorePresentationOnNewEvents = 'userdata-bbb_force_restore_presentation_on_new_events=true'; exports.bannerText = 'bannerText=some text'; -exports.color = '111111'; +exports.color = 'FFFF00'; exports.bannerColor = `bannerColor=%23${this.color}`; diff --git a/bigbluebutton-html5/tests/puppeteer/customparameters/customparameters.js b/bigbluebutton-html5/tests/puppeteer/customparameters/customparameters.js index 005cb7e742379d65f9c48a3cacd8214afd5d0ff8..a9cee89048315b8fc4cfb79806644ac23ca0edcd 100644 --- a/bigbluebutton-html5/tests/puppeteer/customparameters/customparameters.js +++ b/bigbluebutton-html5/tests/puppeteer/customparameters/customparameters.js @@ -22,29 +22,32 @@ class CustomParameters { } async autoJoin(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator' }, customParameter, testName); await this.page1.screenshot(`${testName}`, `01-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); await this.page1.waitForSelector('div[data-test="chatMessages"]', { timeout: 5000 }); if (await this.page1.page.evaluate(util.getTestElement, cpe.audioModal) === false) { await this.page1.screenshot(`${testName}`, `02-fail-${testName}`); + this.page1.logger(testName, ' failed'); return false; } const resp = await this.page1.page.evaluate(util.getTestElement, cpe.audioModal) === true; await this.page1.screenshot(`${testName}`, `02-success-${testName}`); + this.page1.logger(testName, ' passed'); return resp === true; } async listenOnlyMode(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator' }, customParameter, testName); await this.page1.screenshot(`${testName}`, `01-page1-${testName}`); await this.page2.init(args, this.page1.meetingId, { ...params, fullName: 'Attendee', moderatorPW: '' }, customParameter, testName); await this.page2.screenshot(`${testName}`, `01-page2-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); if (await this.page2.page.$('[data-test="audioModalHeader"]')) { await this.page2.screenshot(`${testName}`, `02-fail-${testName}`); + this.page1.logger(testName, ' failed'); return false; } await this.page1.page.waitFor(cpe.echoTestYesButton); @@ -55,32 +58,33 @@ class CustomParameters { await this.page1.screenshot(`${testName}`, `03-success-page1-${testName}`); const resp2 = await util.listenOnlyMode(this.page2); await this.page2.screenshot(`${testName}`, `03-success-page2-${testName}`); - this.page1.logger({ resp1, resp2 }); + this.page1.logger(testName, ' passed'); return resp1 === true && resp2 === true; } async forceListenOnly(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page2.logger('before init ', testName); await this.page2.init(args, meetingId, { ...params, fullName: 'Attendee', moderatorPW: '' }, customParameter, testName); await this.page2.screenshot(`${testName}`, `01-${testName}`); - this.page1.logger('after init'); + this.page2.logger('after init ', testName); if (await this.page2.page.$('[data-test="audioModalHeader"]')) { await this.page2.screenshot(`${testName}`, `02-fail-${testName}`); + this.page2.logger(testName, ' failed'); return false; } await this.page2.waitForSelector(cpe.audioNotification); await this.page2.screenshot(`${testName}`, `02-success-${testName}`); const resp = await util.forceListenOnly(this.page2); await this.page2.screenshot(`${testName}`, `03-success-${testName}`); - this.page1.logger(resp); + this.page2.logger(testName, ' passed'); return resp === true; } async skipCheck(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator' }, customParameter, testName); await this.page1.screenshot(`${testName}`, `01-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); this.page1.logger('connecting with microphone'); await this.page1.joinMicrophoneWithoutEchoTest(); await this.page1.screenshot(`${testName}`, `02-${testName}`); @@ -89,41 +93,39 @@ class CustomParameters { this.page1.logger('before if condition'); if (await this.page1.page.evaluate(util.countTestElements, cpe.echoTestYesButton) === true) { await this.page1.screenshot(`${testName}`, `04-fail-${testName}`); - this.page1.logger('fail'); + this.page1.logger(testName, ' failed'); return false; } - this.page1.logger('before skipCheck'); const resp = await this.page1.page.evaluate(util.countTestElements, cpe.echoTestYesButton) === false; await this.page1.screenshot(`${testName}`, `04-success-${testName}`); - this.page1.logger('after skipCheck'); - this.page1.logger(resp); + this.page1.logger(testName, ' passed'); return resp === true; } async clientTitle(testName, args, meetingId, customParameter) { testName = 'clientTitle'; - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator' }, customParameter, testName); await this.page1.screenshot(`${testName}`, `01-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); await this.page1.waitForSelector('button[aria-label="Microphone"]'); await this.page1.screenshot(`${testName}`, `02-${testName}`); if (await !(await this.page1.page.title()).includes(c.docTitle)) { await this.page1.screenshot(`${testName}`, `03-fail-${testName}`); - this.page1.logger('fail'); + this.page1.logger(testName, ' failed'); return false; } const resp = await (await this.page1.page.title()).includes(c.docTitle); await this.page1.screenshot(`${testName}`, `03-success-${testName}`); - this.page1.logger(resp); + this.page1.logger(testName, ' passed'); return resp === true; } async askForFeedbackOnLogout(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator' }, customParameter, testName); await this.page1.screenshot(`${testName}`, `01-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); await this.page1.closeAudioModal(); await this.page1.screenshot(`${testName}`, `02-${testName}`); await this.page1.logoutFromMeeting(); @@ -133,40 +135,40 @@ class CustomParameters { this.page1.logger('audio modal closed'); if (await this.page1.page.evaluate(util.countTestElements, cpe.rating) === false) { await this.page1.screenshot(`${testName}`, `05-fail-${testName}`); - this.page1.logger('fail'); + this.page1.logger(testName, ' failed'); return false; } const resp = await this.page1.page.evaluate(util.countTestElements, cpe.rating) === true; await this.page1.screenshot(`${testName}`, `05-success-${testName}`); - this.page1.logger(resp); + this.page1.logger(testName, ' passed'); return resp === true; } async displayBrandingArea(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator' }, customParameter, testName); await this.page1.screenshot(`${testName}`, `01-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); await this.page1.closeAudioModal(); await this.page1.screenshot(`${testName}`, `02-${testName}`); this.page1.logger('audio modal closed'); await this.page1.waitForSelector('div[data-test="userListContent"]'); if (await this.page1.page.evaluate(util.countTestElements, cpe.brandingAreaLogo) === false) { - this.page1.logger('fail'); await this.page1.screenshot(`${testName}`, `03-fail-${testName}`); + this.page1.logger(testName, ' failed'); return false; } const resp = await this.page1.page.evaluate(util.countTestElements, cpe.brandingAreaLogo) === true; - this.page1.logger(resp); await this.page1.screenshot(`${testName}`, `03-success-${testName}`); + this.page1.logger(testName, ' passed'); return resp === true; } async shortcuts(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator' }, customParameter, testName); await this.page1.screenshot(`${testName}`, `01-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); await this.page1.closeAudioModal(); await this.page1.screenshot(`${testName}`, `02-${testName}`); this.page1.logger('audio modal closed'); @@ -174,69 +176,75 @@ class CustomParameters { await this.page1.page.keyboard.down('Alt'); await this.page1.page.keyboard.press('O'); if (await this.page1.page.evaluate(util.getTestElement, cpe.verticalListOptions) === false) { - this.page1.logger('fail'); await this.page1.screenshot(`${testName}`, `03-fail-${testName}`); + this.page1.logger(testName, ' failed'); return false; } const resp = await this.page1.page.evaluate(util.getTestElement, cpe.verticalListOptions) === true; - this.page1.logger(resp); await this.page1.screenshot(`${testName}`, `03-success-${testName}`); + this.page1.logger(testName, ' passed'); return resp === true; } async enableScreensharing(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator' }, customParameter, testName); await this.page1.closeAudioModal(); await this.page1.screenshot(`${testName}`, `01-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); if (await this.page1.page.evaluate(util.getTestElement, cpe.screenShareButton) === false) { await this.page1.screenshot(`${testName}`, `02-fail-${testName}`); + this.page1.logger(testName, ' failed'); return false; } const resp = await this.page1.page.evaluate(util.getTestElement, cpe.screenShareButton) === true; await this.page1.screenshot(`${testName}`, `02-success-${testName}`); + this.page1.logger(testName, ' passed'); return resp === true; } async enableVideo(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator' }, customParameter, testName); await this.page1.closeAudioModal(); await this.page1.screenshot(`${testName}`, `01-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); if (await this.page1.page.evaluate(util.getTestElement, cpe.shareWebcamButton) === false) { await this.page1.screenshot(`${testName}`, `02-fail-${testName}`); + this.page1.logger(testName, ' failed'); return false; } const resp = await this.page1.page.evaluate(util.getTestElement, cpe.shareWebcamButton) === true; await this.page1.screenshot(`${testName}`, `02-success-${testName}`); + this.page1.logger(testName, ' passed'); return resp === true; } async autoShareWebcam(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator' }, customParameter, testName); await this.page1.screenshot(`${testName}`, `01-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); await this.page1.closeAudioModal(); await this.page1.screenshot(`${testName}`, `02-${testName}`); if (await this.page1.page.evaluate(util.getTestElement, cpe.webcamSettingsModal) === true) { await this.page1.screenshot(`${testName}`, `03-fail-${testName}`); + this.page1.logger(testName, ' failed'); return false; } const resp = await this.page1.page.evaluate(util.getTestElement, cpe.webcamSettingsModal) === false; await this.page1.screenshot(`${testName}`, `03-success-${testName}`); + this.page1.logger(testName, ' passed'); return resp === true; } async multiUserPenOnly(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator1' }, customParameter, testName); await this.page2.init(args, this.page1.meetingId, { ...params, fullName: 'Moderator2' }, customParameter, testName); await this.page1.screenshot(`${testName}`, `01-page1-${testName}`); await this.page2.screenshot(`${testName}`, `01-page2-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); await this.page1.closeAudioModal(); await this.page2.closeAudioModal(); await this.page1.screenshot(`${testName}`, `02-page1-${testName}`); @@ -249,18 +257,20 @@ class CustomParameters { await this.page2.screenshot(`${testName}`, `04-page2-${testName}`); if (await this.page2.page.evaluate(async () => await document.querySelectorAll('[aria-label="Tools"]')[0].parentElement.childElementCount === 2)) { await this.page2.screenshot(`${testName}`, `05-page2-fail-${testName}`); + this.page1.logger(testName, ' failed'); return false; } const resp = await this.page2.page.evaluate(async () => await document.querySelectorAll('[aria-label="Tools"]')[0].parentElement.childElementCount === 1); await this.page2.screenshot(`${testName}`, `05-page2-success-${testName}`); + this.page1.logger(testName, ' passed'); return resp === true; } async presenterTools(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator1' }, customParameter, testName); await this.page1.screenshot(`${testName}`, `01-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); await this.page1.closeAudioModal(); await this.page1.screenshot(`${testName}`, `02-${testName}`); await this.page1.waitForSelector(cpe.tools); @@ -268,20 +278,22 @@ class CustomParameters { await this.page1.screenshot(`${testName}`, `03-${testName}`); if (await this.page1.page.evaluate(async () => await document.querySelectorAll('[aria-label="Tools"]')[0].parentElement.querySelector('[class^="toolbarList--"]').childElementCount === 7)) { await this.page1.screenshot(`${testName}`, `04-fail-${testName}`); + this.page1.logger(testName, ' failed'); return false; } const resp = await this.page1.page.evaluate(async () => await document.querySelectorAll('[aria-label="Tools"]')[0].parentElement.querySelector('[class^="toolbarList--"]').childElementCount === 2); await this.page1.screenshot(`${testName}`, `04-success-${testName}`); + this.page1.logger(testName, ' passed'); return resp === true; } async multiUserTools(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator1' }, customParameter, testName); await this.page2.init(args, this.page1.meetingId, { ...params, fullName: 'Moderator2' }, customParameter, testName); await this.page1.screenshot(`${testName}`, `01-page1-${testName}`); await this.page2.screenshot(`${testName}`, `01-page2-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); await this.page1.closeAudioModal(); await this.page2.closeAudioModal(); await this.page1.screenshot(`${testName}`, `02-page1-${testName}`); @@ -294,150 +306,190 @@ class CustomParameters { await this.page2.screenshot(`${testName}`, `04-page2-${testName}`); if (await this.page2.page.evaluate(async () => await document.querySelectorAll('[aria-label="Tools"]')[0].parentElement.querySelector('[class^="toolbarList--"]').childElementCount === 7)) { await this.page2.screenshot(`${testName}`, `05-page2-fail-${testName}`); + this.page1.logger(testName, ' failed'); return false; } const resp = await this.page2.page.evaluate(async () => await document.querySelectorAll('[aria-label="Tools"]')[0].parentElement.querySelector('[class^="toolbarList--"]').childElementCount === 2); await this.page2.screenshot(`${testName}`, `05-page2-success-${testName}`); + this.page1.logger(testName, ' passed'); return resp === true; } async customStyle(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator1' }, customParameter, testName); await this.page1.screenshot(`${testName}`, `01-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); await this.page1.closeAudioModal(); await this.page1.waitForSelector(cpe.whiteboard); await this.page1.screenshot(`${testName}`, `02-${testName}`); - if (await this.page1.page.evaluate(util.getTestElement, cpe.actions) === false) { + const isHidden = await this.page1.page.$eval('[class="presentationTitle--1LT79g"]', (elem) => { + return elem.offsetHeight == 0 + }); + if (isHidden === false) { await this.page1.screenshot(`${testName}`, `03-fail-${testName}`); + this.page1.logger(testName, ' failed'); return false; + } else if (isHidden === true) { + await this.page1.screenshot(`${testName}`, `03-success-${testName}`); + const resp = isHidden; + this.page1.logger(testName, ' passed'); + return resp === true; } - const resp = await this.page1.page.evaluate(util.getTestElement, cpe.actions) === true; - await this.page1.screenshot(`${testName}`, `03-success-${testName}`); - return resp === true; } async customStyleUrl(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator1' }, customParameter, testName); await this.page1.screenshot(`${testName}`, `01-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); await this.page1.closeAudioModal(); await this.page1.waitForSelector(cpe.whiteboard); - await this.page1.screenshot(`${testName}`, `02-${testName}`); - if (await this.page1.page.evaluate(util.getTestElement, cpe.actions) === false) { + await this.page1.screenshot(`${testName}`, `02-${testName}`); + const isHidden = await this.page1.page.$eval('[class="presentationTitle--1LT79g"]', (elem) => { + return elem.offsetHeight == 0 + }); + if (isHidden === false) { await this.page1.screenshot(`${testName}`, `03-fail-${testName}`); + this.page1.logger(testName, ' failed'); return false; + } else if (isHidden === true) { + await this.page1.screenshot(`${testName}`, `03-success-${testName}`); + const resp = isHidden; + this.page1.logger(testName, ' passed'); + return resp === true; } - const resp = await this.page1.page.evaluate(util.getTestElement, cpe.actions) === true; - await this.page1.screenshot(`${testName}`, `03-success-${testName}`); - return resp === true; } async autoSwapLayout(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator1' }, customParameter, testName); await this.page1.screenshot(`${testName}`, `01-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); await this.page1.closeAudioModal(); await this.page1.waitForSelector(cpe.container); await this.page1.screenshot(`${testName}`, `02-${testName}`); - if (await this.page1.page.evaluate(util.getTestElement, cpe.restorePresentation) === false) { + const isNotHidden = await this.page1.page.$eval(cpe.restorePresentation, (elem) => { + return elem.offsetHeight !== 0 + }); + console.log(isNotHidden); + if (isNotHidden === false) { await this.page1.screenshot(`${testName}`, `03-fail-${testName}`); + this.page1.logger(testName, ' failed'); return false; + } else if (isNotHidden === true) { + await this.page1.screenshot(`${testName}`, `03-success-${testName}`); + const resp = isNotHidden; + this.page1.logger(testName, ' passed'); + return resp === true; } - const resp = await this.page1.page.evaluate(util.getTestElement, cpe.restorePresentation) === true; - await this.page1.screenshot(`${testName}`, `03-success-${testName}`); - return resp === true; } async hidePresentation(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator1' }, customParameter, testName); await this.page1.screenshot(`${testName}`, `01-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); await this.page1.closeAudioModal(); await this.page1.waitForSelector(cpe.actions); await this.page1.screenshot(`${testName}`, `02-${testName}`); if (await this.page1.page.evaluate(util.countTestElements, cpe.defaultContent) === false) { await this.page1.screenshot(`${testName}`, `03-fail-${testName}`); + this.page1.logger(testName, ' failed'); return false; } const resp = await this.page1.page.evaluate(util.countTestElements, cpe.defaultContent) === true; await this.page1.screenshot(`${testName}`, `03-success-${testName}`); + this.page1.logger(testName, ' passed'); return resp === true; } async bannerText(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator1' }, customParameter, testName); await this.page1.screenshot(`${testName}`, `01-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); await this.page1.closeAudioModal(); await this.page1.waitForSelector(cpe.actions); await this.page1.screenshot(`${testName}`, `02-${testName}`); if (await this.page1.page.evaluate(util.countTestElements, cpe.notificationBar) === false) { await this.page1.screenshot(`${testName}`, `03-fail-${testName}`); + this.page1.logger(testName, ' failed'); return false; } const resp = await this.page1.page.evaluate(util.countTestElements, cpe.notificationBar) === true; await this.page1.screenshot(`${testName}`, `03-success-${testName}`); + this.page1.logger(testName, ' passed'); return resp === true; } async bannerColor(testName, args, meetingId, customParameter, colorToRGB) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator1' }, customParameter, testName); await this.page1.screenshot(`${testName}`, `01-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); await this.page1.closeAudioModal(); await this.page1.waitForSelector(cpe.notificationBar); await this.page1.screenshot(`${testName}`, `02-${testName}`); - const resp = await this.page1.page.evaluate(() => getComputedStyle('div[class^="notificationsBar--"]').backgroundColor); - await this.page1.screenshot(`${testName}`, `03-${testName}`); - return resp === colorToRGB; + const notificationBarColor = await this.page1.page.$eval('div[class^="notificationsBar--"]', (elem) => { + return getComputedStyle(elem).backgroundColor + }); + console.log('colorToRGB => ', colorToRGB) + console.log('notificationBarColor => ', notificationBarColor) + if (notificationBarColor !== colorToRGB) { + await this.page1.screenshot(`${testName}`, `03-fail-${testName}`); + this.page1.logger(testName, ' failed'); + return false; + } else if (notificationBarColor === colorToRGB) { + await this.page1.screenshot(`${testName}`, `03-success-${testName}`); + this.page1.logger(testName, ' passed'); + return true; + } } async hideAndSwapPresentation(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator1' }, customParameter, testName); await this.page1.screenshot(`${testName}`, `01-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); await this.page1.closeAudioModal(); await this.page1.waitForSelector(cpe.container); if (await this.page1.page.evaluate(util.countTestElements, cpe.restorePresentation) === false && await this.page1.page.evaluate(util.countTestElements, cpe.defaultContent) === false) { await this.page1.screenshot(`${testName}`, `03-fail-${testName}`); + this.page1.logger(testName, ' failed'); return false; } const resp = await this.page1.page.evaluate(util.countTestElements, cpe.restorePresentation) === true && await this.page1.page.evaluate(util.countTestElements, cpe.defaultContent) === true; await this.page1.screenshot(`${testName}`, `03-success-${testName}`); + this.page1.logger(testName, ' passed'); return resp === true; } async showPublicChatOnLogin(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator1' }, customParameter, testName); await this.page1.screenshot(`${testName}`, `01-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); await this.page1.closeAudioModal(); await this.page1.waitForSelector(cpe.container); if (await this.page1.page.evaluate(util.countTestElements, cpe.chat) === true) { await this.page1.screenshot(`${testName}`, `03-fail-${testName}`); + this.page1.logger(testName, ' failed'); return false; } const resp = await this.page1.page.evaluate(util.countTestElements, cpe.chat) === false; await this.page1.screenshot(`${testName}`, `03-success-${testName}`); + this.page1.logger(testName, ' passed'); return resp === true; } async forceRestorePresentationOnNewEvents(testName, args, meetingId, customParameter) { - this.page1.logger('before init'); + this.page1.logger('before init ', testName); await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator1' }, customParameter, testName); await this.page2.init(args, this.page1.meetingId, { ...params, fullName: 'Viewer1', moderatorPW: '' }, customParameter, testName); await this.page1.screenshot(`${testName}`, `01-page1-${testName}`); await this.page2.screenshot(`${testName}`, `01-page2-${testName}`); - this.page1.logger('after init'); + this.page1.logger('after init ', testName); await this.page1.closeAudioModal(); await this.page1.screenshot(`${testName}`, `02-page1-${testName}`); await this.page2.closeAudioModal(); @@ -467,11 +519,12 @@ class CustomParameters { if (zoomInCase === true && zoomOutCase === true && pollCase === true && previousSlideCase === true && nextSlideCase === true && annotationCase === true && await this.page2.page.evaluate(util.countTestElements, cpe.restorePresentation) === true) { await this.page2.screenshot(`${testName}`, `08-page2-fail-${testName}`); - this.page1.logger('fail'); + this.page1.logger(testName, ' failed'); return false; } await this.page2.page.evaluate(util.countTestElements, cpe.restorePresentation) === false; await this.page2.screenshot(`${testName}`, `08-page2-success-${testName}`); + this.page1.logger(testName, ' passed'); return true; } diff --git a/bigbluebutton-html5/tests/puppeteer/customparameters/util.js b/bigbluebutton-html5/tests/puppeteer/customparameters/util.js index e1a440da8a7436471950d5580f50d58bb213f128..f5ef22abeb46a7d02c75cfff1a5a69d56e145ed0 100644 --- a/bigbluebutton-html5/tests/puppeteer/customparameters/util.js +++ b/bigbluebutton-html5/tests/puppeteer/customparameters/util.js @@ -110,7 +110,8 @@ async function poll(test) { async function previousSlide(test) { try { - await test.page.evaluate(() => document.querySelectorAll('button[aria-describedby="prevSlideDesc"]')[0].click()); + await test.waitForSelector('button[data-test="prevSlide"]'); + await test.click('button[data-test="prevSlide"]', true); return true; } catch (e) { console.log(e); @@ -120,7 +121,8 @@ async function previousSlide(test) { async function nextSlide(test) { try { - await test.page.evaluate(() => document.querySelectorAll('button[aria-describedby="nextSlideDesc"]')[0].click()); + await test.waitForSelector('button[data-test="nextSlide"]'); + await test.click('button[data-test="nextSlide"]', true); return true; } catch (e) { console.log(e); diff --git a/bigbluebutton-html5/tests/puppeteer/notifications.obj.js b/bigbluebutton-html5/tests/puppeteer/notifications.obj.js new file mode 100644 index 0000000000000000000000000000000000000000..2a22036d36eee57a611c14fd78fa61109b89b0ea --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/notifications.obj.js @@ -0,0 +1,131 @@ +const Notifications = require('./notifications/notifications'); +const ShareScreen = require('./screenshare/screenshare'); +const Audio = require('./audio/audio'); +const Page = require('./core/page'); + +const notificationsTest = () => { + beforeEach(() => { + jest.setTimeout(50000); + }); + + test('Save settings notification', async () => { + const test = new Notifications(); + let response; + try { + const testName = 'saveSettingsNotification'; + response = await test.saveSettingsNotification(testName); + } catch (e) { + console.log(e); + } finally { + await test.close(test.page1, test.page2); + await test.page1.logger('Save Setting notification !'); + } + expect(response).toBe(true); + }); + + test('Public Chat notification', async () => { + const test = new Notifications(); + let response; + try { + const testName = 'publicChatNotification'; + response = await test.publicChatNotification(testName); + } catch (e) { + console.log(e); + } finally { + await test.close(test.page1, test.page2); + await test.page1.logger('Public Chat notification !'); + } + expect(response).toBe(true); + }); + + test('Private Chat notification', async () => { + const test = new Notifications(); + let response; + try { + const testName = 'privateChatNotification'; + response = await test.privateChatNotification(testName); + } catch (e) { + console.log(e); + } finally { + await test.close(test.page1, test.page2); + await test.page1.logger('Private Chat notification !'); + } + expect(response).toBe(true); + }); + + test('User join notification', async () => { + const test = new Notifications(); + let response; + try { + const testName = 'userJoinNotification'; + response = await test.getUserJoinPopupResponse(testName); + } catch (e) { + console.log(e); + } finally { + await test.closePages(); + await test.page1.logger('User join notification !'); + } + expect(response).toBe(true); + }); + + test('Presentation upload notification', async () => { + const test = new Notifications(); + let response; + try { + const testName = 'uploadPresentationNotification'; + response = await test.fileUploaderNotification(testName); + } catch (e) { + console.log(e); + } finally { + await test.closePage(test.page3); + await test.page3.logger('Presentation upload notification !'); + } + expect(response).toBe(true); + }); + + test('Poll results notification', async () => { + const test = new Notifications(); + let response; + try { + const testName = 'pollResultsNotification'; + response = await test.publishPollResults(testName); + } catch (e) { + console.log(e); + } finally { + await test.closePage(test.page3); + await test.page3.logger('Poll results notification !'); + } + expect(response).toContain('Poll results were published to Public Chat and Whiteboard'); + }); + + test('Screenshare notification', async () => { + const page = new Notifications(); + let response; + try { + const testName = 'screenShareNotification'; + response = await page.screenshareToast(testName); + } catch (e) { + console.log(e); + } finally { + await page.closePage(page.page3); + await page.page3.logger('Screenshare notification !'); + } + expect(response).toBe('Screenshare has started'); + }); + + test('Audio notifications', async () => { + const test = new Notifications(); + let response; + try { + const testName = 'audioNotification'; + response = await test.audioNotification(testName); + } catch (e) { + console.log(e); + } finally { + await test.closePage(test.page3); + await test.page3.logger('Audio notification !'); + } + expect(response).toBe(true); + }); +}; +module.exports = exports = notificationsTest; diff --git a/bigbluebutton-html5/tests/puppeteer/notifications.test.js b/bigbluebutton-html5/tests/puppeteer/notifications.test.js index 9d55f042c5e784d462460de07de4c453dddb378e..ccf4d54573236c85f177ea2c422e9d00f699afae 100644 --- a/bigbluebutton-html5/tests/puppeteer/notifications.test.js +++ b/bigbluebutton-html5/tests/puppeteer/notifications.test.js @@ -1,130 +1,3 @@ -const Notifications = require('./notifications/notifications'); -const ShareScreen = require('./screenshare/screenshare'); -const Audio = require('./audio/audio'); -const Page = require('./core/page'); +const notificationsTest = require('./notifications.obj'); -describe('Notifications', () => { - beforeEach(() => { - jest.setTimeout(30000); - }); - - test('Save settings notification', async () => { - const test = new Notifications(); - let response; - try { - const testName = 'saveSettingsNotification'; - response = await test.saveSettingsNotification(testName); - } catch (e) { - console.log(e); - } finally { - await test.close(test.page1, test.page2); - await test.page1.logger('Save Setting notification !'); - } - expect(response).toBe(true); - }); - - test('Public Chat notification', async () => { - const test = new Notifications(); - let response; - try { - const testName = 'publicChatNotification'; - response = await test.publicChatNotification(testName); - } catch (e) { - console.log(e); - } finally { - await test.close(test.page1, test.page2); - await test.page1.logger('Public Chat notification !'); - } - expect(response).toBe(true); - }); - - test('Private Chat notification', async () => { - const test = new Notifications(); - let response; - try { - const testName = 'privateChatNotification'; - response = await test.privateChatNotification(testName); - } catch (e) { - console.log(e); - } finally { - await test.close(test.page1, test.page2); - await test.page1.logger('Private Chat notification !'); - } - expect(response).toBe(true); - }); - - test('User join notification', async () => { - const test = new Notifications(); - let response; - try { - const testName = 'userJoinNotification'; - response = await test.getUserJoinPopupResponse(testName); - } catch (e) { - console.log(e); - } finally { - await test.closePages(); - await test.page1.logger('User join notification !'); - } - expect(response).toBe('User4 joined the session'); - }); - - test('Presentation upload notification', async () => { - const test = new Notifications(); - let response; - try { - const testName = 'uploadPresentationNotification'; - response = await test.fileUploaderNotification(testName); - } catch (e) { - console.log(e); - } finally { - await test.closePage(test.page3); - await test.page3.logger('Presentation upload notification !'); - } - expect(response).toContain('Current presentation'); - }); - - test('Poll results notification', async () => { - const test = new Notifications(); - let response; - try { - const testName = 'pollResultsNotification'; - response = await test.publishPollResults(testName); - } catch (e) { - console.log(e); - } finally { - await test.closePage(test.page3); - await test.page3.logger('Poll results notification !'); - } - expect(response).toContain('Poll results were published to Public Chat and Whiteboard'); - }); - - test('Screenshare notification', async () => { - const page = new Notifications(); - let response; - try { - const testName = 'screenShareNotification'; - response = await page.screenshareToast(testName); - } catch (e) { - console.log(e); - } finally { - await page.closePage(page.page3); - await page.page3.logger('Screenshare notification !'); - } - expect(response).toBe('Screenshare has started'); - }); - - test('Audio notifications', async () => { - const test = new Notifications(); - let response; - try { - const testName = 'audioNotification'; - response = await test.audioNotification(testName); - } catch (e) { - console.log(e); - } finally { - await test.closePage(test.page3); - await test.page3.logger('Audio notification !'); - } - expect(response).toBe(true); - }); -}); +describe('Notifications', notificationsTest); diff --git a/bigbluebutton-html5/tests/puppeteer/notifications/notifications.js b/bigbluebutton-html5/tests/puppeteer/notifications/notifications.js index 524c78fc1e840ce6891a8078ec0c7c548b3da419..b46adab978e4d5dcc5f7ac23d2cb7b248e665ff0 100644 --- a/bigbluebutton-html5/tests/puppeteer/notifications/notifications.js +++ b/bigbluebutton-html5/tests/puppeteer/notifications/notifications.js @@ -5,6 +5,7 @@ const params = require('../params'); const util = require('./util'); const utilScreenShare = require('../screenshare/util'); // utils imported from screenshare folder const ne = require('./elements'); +const pe = require('../presentation/elements'); const we = require('../whiteboard/elements'); class Notifications extends MultiUsers { @@ -28,7 +29,7 @@ class Notifications extends MultiUsers { } async initUser4() { - await this.page4.init(Page.getArgs(), this.page3.meetingId, { ...params, fullName: 'User4' }); + await this.page4.init(Page.getArgs(), this.page3.meetingId, { ...params, fullName: 'User' }, undefined, undefined); } // Save Settings toast notification @@ -96,12 +97,19 @@ class Notifications extends MultiUsers { await this.page3.screenshot(`${testName}`, `02-page03-audio-modal-closed-${testName}`); await this.userJoinNotification(this.page3); await this.page3.screenshot(`${testName}`, `03-page03-after-user-join-notification-activation-${testName}`); - await this.initUser4(undefined); + await this.initUser4(); await this.page4.closeAudioModal(); await this.page3.waitForSelector(ne.smallToastMsg); - const response = await util.getOtherToastValue(this.page3); - await this.page3.screenshot(`${testName}`, `04-page03-user-join-toast-${testName}`); - return response; + try { + await this.page3.page.waitForFunction( + 'document.querySelector("body").innerText.includes("User joined the session")', + ); + await this.page3.screenshot(`${testName}`, `04-page03-user-join-toast-${testName}`); + return true; + } catch (e) { + console.log(e); + return false; + } } // File upload notification @@ -112,17 +120,31 @@ class Notifications extends MultiUsers { await this.page3.screenshot(`${testName}`, `02-page03-audio-modal-closed-${testName}`); await util.uploadFileMenu(this.page3); await this.page3.screenshot(`${testName}`, `03-page03-upload-file-menu-${testName}`); - await this.page3.waitForSelector(ne.fileUploadDropZone); - const inputUploadHandle = await this.page3.page.$('input[type=file]'); - await inputUploadHandle.uploadFile(path.join(__dirname, '../media/DifferentSizes.pdf')); - await this.page3.page.evaluate(util.clickTestElement, ne.modalConfirmButton); + await this.page3.waitForSelector(pe.fileUpload); + const fileUpload = await this.page3.page.$(pe.fileUpload); + await fileUpload.uploadFile(path.join(__dirname, '../media/DifferentSizes.pdf')); + await this.page3.page.waitForFunction( + 'document.querySelector("body").innerText.includes("To be uploaded ...")', + ); + await this.page3.page.waitForSelector(pe.upload); + await this.page3.page.click(pe.upload); + await this.page3.page.waitForFunction( + 'document.querySelector("body").innerText.includes("Converting file")', + ); await this.page3.screenshot(`${testName}`, `04-page03-file-uploaded-and-ready-${testName}`); await this.page3.waitForSelector(ne.smallToastMsg); await this.page3.waitForSelector(we.whiteboard); await this.page3.screenshot(`${testName}`, `05-page03-presentation-changed-${testName}`); - const resp = await util.getLastToastValue(this.page3); - await this.page3.screenshot(`${testName}`, `06-page03-presentation-change-toast-${testName}`); - return resp; + try { + await this.page3.page.waitForFunction( + 'document.querySelector("body").innerText.includes("Current presentation")', + ); + await this.page3.screenshot(`${testName}`, `06-page03-presentation-change-toast-${testName}`); + return true; + } catch (e) { + console.log(e); + return false; + } } // Publish Poll Results notification diff --git a/bigbluebutton-html5/tests/puppeteer/package-lock.json b/bigbluebutton-html5/tests/puppeteer/package-lock.json index 363ad501167a4383d5ea91f9e50de4c570bdd3c8..6024bd03e254d12fe53626632f1c6ed6368950df 100644 --- a/bigbluebutton-html5/tests/puppeteer/package-lock.json +++ b/bigbluebutton-html5/tests/puppeteer/package-lock.json @@ -588,15 +588,6 @@ "babel-template": "^6.24.1" } }, - "babel-jest": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-23.6.0.tgz", - "integrity": "sha512-lqKGG6LYXYu+DQh/slrQ8nxXQkEkhugdXsU6St7GmhVS7Ilc/22ArwqXNJrf0QaOBjZB0360qZMwXqDYQHXaew==", - "requires": { - "babel-plugin-istanbul": "^4.1.6", - "babel-preset-jest": "^23.2.0" - } - }, "babel-messages": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", @@ -2229,6 +2220,17 @@ "jest-validate": "^23.6.0", "micromatch": "^2.3.11", "pretty-format": "^23.6.0" + }, + "dependencies": { + "babel-jest": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-23.6.0.tgz", + "integrity": "sha512-lqKGG6LYXYu+DQh/slrQ8nxXQkEkhugdXsU6St7GmhVS7Ilc/22ArwqXNJrf0QaOBjZB0360qZMwXqDYQHXaew==", + "requires": { + "babel-plugin-istanbul": "^4.1.6", + "babel-preset-jest": "^23.2.0" + } + } } }, "jest-diff": { diff --git a/bigbluebutton-html5/tests/puppeteer/package.json b/bigbluebutton-html5/tests/puppeteer/package.json index 762ccb51cac1b5cfcb78c40ce1a201797f7f8688..6496d2c0931599aecfb48bf2bc048aca066673cf 100644 --- a/bigbluebutton-html5/tests/puppeteer/package.json +++ b/bigbluebutton-html5/tests/puppeteer/package.json @@ -9,7 +9,7 @@ "axios": "^0.18.0", "dotenv": "^6.0.0", "jest": "^23.5.0", - "puppeteer": "2.0.0", + "puppeteer": "3.0.2", "moment": "^2.24.0", "sha1": "^1.1.1" } diff --git a/bigbluebutton-html5/tests/puppeteer/presentation.obj.js b/bigbluebutton-html5/tests/puppeteer/presentation.obj.js new file mode 100644 index 0000000000000000000000000000000000000000..c63d6a106681057dfd837149bb7778c6517dc3c3 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/presentation.obj.js @@ -0,0 +1,41 @@ +const Page = require('./core/page'); +const Slide = require('./presentation/slide'); +const Upload = require('./presentation/upload'); + +const presentationTest = () => { + beforeEach(() => { + jest.setTimeout(50000); + }); + + test('Skip slide', async () => { + const test = new Slide(); + let response; + try { + await test.init(Page.getArgs()); + await test.closeAudioModal(); + response = await test.test(); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); + + test('Upload presentation', async () => { + const test = new Upload(); + let response; + try { + const testName = 'uploadPresentation'; + await test.init(Page.getArgs()); + await test.closeAudioModal(); + response = await test.test(testName); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); +}; +module.exports = exports = presentationTest; diff --git a/bigbluebutton-html5/tests/puppeteer/presentation.test.js b/bigbluebutton-html5/tests/puppeteer/presentation.test.js index 45d9bc751774707991e403d258c28e01c42165dc..063507a54acae73f684eaf79186afdcf0a9b82e3 100644 --- a/bigbluebutton-html5/tests/puppeteer/presentation.test.js +++ b/bigbluebutton-html5/tests/puppeteer/presentation.test.js @@ -1,39 +1,3 @@ -const Page = require('./core/page'); -const Slide = require('./presentation/slide'); -const Upload = require('./presentation/upload'); +const presentationTest = require('./presentation.obj'); -describe('Presentation', () => { - beforeEach(() => { - jest.setTimeout(30000); - }); - - test('Skip slide', async () => { - const test = new Slide(); - let response; - try { - await test.init(Page.getArgs()); - await test.closeAudioModal(); - response = await test.test(); - } catch (e) { - console.log(e); - } finally { - await test.close(); - } - expect(response).toBe(true); - }); - - test('Upload presentation', async () => { - const test = new Upload(); - let response; - try { - await test.init(Page.getArgs()); - await test.closeAudioModal(); - response = await test.test(); - } catch (e) { - console.log(e); - } finally { - await test.close(); - } - expect(response).toBe(true); - }); -}); +describe('Presentation', presentationTest); diff --git a/bigbluebutton-html5/tests/puppeteer/presentation/elements.js b/bigbluebutton-html5/tests/puppeteer/presentation/elements.js index 48dc684750f70c31a027f3567b9edb137a613310..8491b989334f38f167c18bf417eda3a2c9aa72ab 100644 --- a/bigbluebutton-html5/tests/puppeteer/presentation/elements.js +++ b/bigbluebutton-html5/tests/puppeteer/presentation/elements.js @@ -2,7 +2,7 @@ exports.presentationToolbarWrapper = '#presentationToolbarWrapper'; exports.nextSlide = '[data-test="nextSlide"]'; exports.prevSlide = '[data-test="prevSlide"]'; exports.fileUpload = 'input[type="file"]'; -exports.upload = 'button[aria-label="Upload "]'; +exports.upload = 'button[aria-label="Upload"]'; exports.cancel = 'button[aria-label="Cancel]'; exports.uploadPresentation = '[data-test="uploadPresentation"]'; exports.skipSlide = '[data-test="skipSlide"]'; diff --git a/bigbluebutton-html5/tests/puppeteer/presentation/upload.js b/bigbluebutton-html5/tests/puppeteer/presentation/upload.js index b77237e6a8d0181932a1b296bd7ee966629c060c..8646508e6c3975eabc6e6a68588f72a3ecf76775 100644 --- a/bigbluebutton-html5/tests/puppeteer/presentation/upload.js +++ b/bigbluebutton-html5/tests/puppeteer/presentation/upload.js @@ -8,7 +8,7 @@ class Upload extends Page { super('presentation-upload'); } - async test() { + async test(testName) { await this.waitForSelector(we.whiteboard); await this.waitForSelector(e.skipSlide); @@ -17,34 +17,43 @@ class Upload extends Page { await this.click(ce.actions); await this.click(e.uploadPresentation); + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.screenshot(`${testName}`, `01-before-presentation-upload-[${testName}]`); + } await this.waitForSelector(e.fileUpload); const fileUpload = await this.page.$(e.fileUpload); await fileUpload.uploadFile(`${__dirname}/upload-test.png`); + await this.page.waitForFunction( + 'document.querySelector("body").innerText.includes("To be uploaded ...")', + ); + await this.page.waitForSelector(e.upload); - await this.click(e.upload); + await this.page.click(e.upload); console.log('\nWaiting for the new presentation to upload...'); - // await this.elementRemoved(e.start); - await this.page.waitFor(10000); + await this.page.waitForFunction( + 'document.querySelector("body").innerText.includes("Converting file")', + ); console.log('\nPresentation uploaded!'); - - await this.screenshot(true); + await this.page.waitForFunction( + 'document.querySelector("body").innerText.includes("Current presentation")', + ); + if (process.env.GENERATE_EVIDENCES === 'true') { + await this.screenshot(`${testName}`, `02-after-presentation-upload-[${testName}]`); + } const slides1 = await this.getTestElements(); console.log('\nSlides before presentation upload:'); - console.log(slides0.slideList); - console.log(slides0.svg); + console.log(slides0); console.log('\nSlides after presentation upload:'); - console.log(slides1.slideList); - console.log(slides1.svg); + console.log(slides1); - return slides0.svg !== slides1.svg; + return slides0 !== slides1; } async getTestElements() { - const slides = {}; - slides.svg = await this.page.evaluate(() => document.querySelector('svg g g g').outerHTML); - slides.slideList = await this.page.evaluate(skipSlide => document.querySelector(skipSlide).innerHTML, e.skipSlide); - return slides; + const svg = await this.page.evaluate(async () => await document.querySelector('svg g g g').outerHTML); + console.log(svg); + return svg; } } diff --git a/bigbluebutton-html5/tests/puppeteer/run.sh b/bigbluebutton-html5/tests/puppeteer/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..4ca75b78fdded072883423f62d780af7d5bca0e7 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/run.sh @@ -0,0 +1,88 @@ +#!/bin/bash -e + +usage() { + set +x + cat 1>&2 <<HERE +BBB Health Check + +OPTIONS: + -t <test name: webcamlayout/whiteboard/webcam/virtualizedlist/user/sharednotes/screenshare/presentation/notifications/customparameters/chat/breakout/audio/all> + -h <hostname name of BigBlueButton server> + -s <Shared secret> + + -u Print usage +HERE + +} + +err() { + echo "----"; + echo ERROR: $@ + echo "----"; +} + +main() { + export DEBIAN_FRONTEND=noninteractive + + while builtin getopts "uh:s:t:" opt "${@}"; do + + case $opt in + t) + TEST=$OPTARG + ;; + + h) + HOST=$OPTARG + ;; + + s) + SECRET=$OPTARG + ;; + + u) + usage + exit 0 + ;; + + :) + err "Missing option argument for -$OPTARG" + exit 1 + ;; + + \?) + err "Invalid option: -$OPTARG" >&2 + usage + ;; + esac + done + + if [ -z "$TEST" ]; then + err "No test provided"; + usage + exit 1 + fi + + if [ -z "$HOST" ]; then + err "No host provided"; + usage + exit 1 + fi + + if [ -z "$SECRET" ]; then + err "No scret provided"; + usage + exit 1 + fi + + IS_AUDIO_TEST=false + + if [ "$TEST" = "audio" ]; then + IS_AUDIO_TEST=true; + fi; + +env $(cat tests/puppeteer/.env | xargs) jest $TEST.test.js --color --detectOpenHandles +} + + +main "$@" || exit 1 + diff --git a/bigbluebutton-html5/tests/puppeteer/screenshare.obj.js b/bigbluebutton-html5/tests/puppeteer/screenshare.obj.js new file mode 100644 index 0000000000000000000000000000000000000000..26e6ae7a56920b45758b80233cc62a9683be50e2 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/screenshare.obj.js @@ -0,0 +1,24 @@ +const ShareScreen = require('./screenshare/screenshare'); +const Page = require('./core/page'); + +const screenShareTest = () => { + beforeEach(() => { + jest.setTimeout(30000); + }); + + test('Share screen', async () => { + const test = new ShareScreen(); + let response; + try { + await test.init(Page.getArgsWithVideo()); + await test.closeAudioModal(); + response = await test.test(); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); +}; +module.exports = exports = screenShareTest; diff --git a/bigbluebutton-html5/tests/puppeteer/screenshare.test.js b/bigbluebutton-html5/tests/puppeteer/screenshare.test.js index e1de8577ceb24afc2030ec61d7521ec9bf67bcc1..60f0eeca776a897badb9562c13f0a22721caef78 100644 --- a/bigbluebutton-html5/tests/puppeteer/screenshare.test.js +++ b/bigbluebutton-html5/tests/puppeteer/screenshare.test.js @@ -1,23 +1,3 @@ -const ShareScreen = require('./screenshare/screenshare'); -const Page = require('./core/page'); +const screenShareTest = require('./screenshare.obj'); -describe('Screen Share', () => { - beforeEach(() => { - jest.setTimeout(30000); - }); - - test('Share screen', async () => { - const test = new ShareScreen(); - let response; - try { - await test.init(Page.getArgsWithVideo()); - await test.closeAudioModal(); - response = await test.test(); - } catch (e) { - console.log(e); - } finally { - await test.close(); - } - expect(response).toBe(true); - }); -}); +describe('Screen Share', screenShareTest); diff --git a/bigbluebutton-html5/tests/puppeteer/sharednotes.obj.js b/bigbluebutton-html5/tests/puppeteer/sharednotes.obj.js new file mode 100644 index 0000000000000000000000000000000000000000..3db37120aa992608d514af6039341b1e26650e25 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/sharednotes.obj.js @@ -0,0 +1,24 @@ +const SharedNotes = require('./notes/sharednotes'); + +const sharedNotesTest = () => { + beforeEach(() => { + jest.setTimeout(30000); + }); + + test('Open Shared notes', async () => { + const test = new SharedNotes(); + let response; + try { + await test.init(); + await test.page1.closeAudioModal(); + await test.page2.closeAudioModal(); + response = await test.test(); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); +}; +module.exports = exports = sharedNotesTest; diff --git a/bigbluebutton-html5/tests/puppeteer/sharednotes.test.js b/bigbluebutton-html5/tests/puppeteer/sharednotes.test.js index b366f579c4e18732e7201325f09b92d82942f908..9dc6dd1c0db58033a7c2187dd31a8a3cb3fd2378 100644 --- a/bigbluebutton-html5/tests/puppeteer/sharednotes.test.js +++ b/bigbluebutton-html5/tests/puppeteer/sharednotes.test.js @@ -1,23 +1,3 @@ -const SharedNotes = require('./notes/sharednotes'); +const sharedNotesTest = require('./sharednotes.obj'); -describe('Shared notes', () => { - beforeEach(() => { - jest.setTimeout(30000); - }); - - test('Open Shared notes', async () => { - const test = new SharedNotes(); - let response; - try { - await test.init(); - await test.page1.closeAudioModal(); - await test.page2.closeAudioModal(); - response = await test.test(); - } catch (e) { - console.log(e); - } finally { - await test.close(); - } - expect(response).toBe(true); - }); -}); +describe('Shared Notes ', sharedNotesTest); diff --git a/bigbluebutton-html5/tests/puppeteer/user.obj.js b/bigbluebutton-html5/tests/puppeteer/user.obj.js new file mode 100644 index 0000000000000000000000000000000000000000..de7d5e569f18d56f9986b7d8257b1ca2274c3f82 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/user.obj.js @@ -0,0 +1,41 @@ +const Page = require('./core/page'); +const Status = require('./user/status'); +const MultiUsers = require('./user/multiusers'); + +const userTest = () => { + beforeEach(() => { + jest.setTimeout(30000); + }); + + test('Change status', async () => { + const test = new Status(); + let response; + try { + await test.init(Page.getArgs()); + await test.closeAudioModal(); + response = await test.test(); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); + + test('Multi user presence check', async () => { + const test = new MultiUsers(); + let response; + try { + await test.init(); + await test.page1.closeAudioModal(); + await test.page2.closeAudioModal(); + response = await test.test(); + } catch (err) { + console.log(err); + } finally { + await test.close(test.page1, test.page2); + } + expect(response).toBe(true); + }); +}; +module.exports = exports = userTest; diff --git a/bigbluebutton-html5/tests/puppeteer/user.test.js b/bigbluebutton-html5/tests/puppeteer/user.test.js index 91025cc63e3a2a8422c405a6474b77fcf1e43c99..fea6e8d04ab7aff6bc2a09629b318232263a4b06 100644 --- a/bigbluebutton-html5/tests/puppeteer/user.test.js +++ b/bigbluebutton-html5/tests/puppeteer/user.test.js @@ -1,40 +1,3 @@ -const Page = require('./core/page'); -const Status = require('./user/status'); -const MultiUsers = require('./user/multiusers'); +const userTest = require('./user.obj'); -describe('User', () => { - beforeEach(() => { - jest.setTimeout(30000); - }); - - test('Change status', async () => { - const test = new Status(); - let response; - try { - await test.init(Page.getArgs()); - await test.closeAudioModal(); - response = await test.test(); - } catch (e) { - console.log(e); - } finally { - await test.close(); - } - expect(response).toBe(true); - }); - - test('Multi user presence check', async () => { - const test = new MultiUsers(); - let response; - try { - await test.init(); - await test.page1.closeAudioModal(); - await test.page2.closeAudioModal(); - response = await test.test(); - } catch (err) { - console.log(err); - } finally { - await test.close(test.page1, test.page2); - } - expect(response).toBe(true); - }); -}); +describe('User', userTest); diff --git a/bigbluebutton-html5/tests/puppeteer/virtualizedlist.obj.js b/bigbluebutton-html5/tests/puppeteer/virtualizedlist.obj.js new file mode 100644 index 0000000000000000000000000000000000000000..9c47a0ca60903a4c2b454c75f20c549373ca6a76 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/virtualizedlist.obj.js @@ -0,0 +1,18 @@ +const VirtualizedList = require('./virtualizedlist/virtualize'); + +const virtualizedListTest = () => { + test('Virtualized Users List', async () => { + const test = new VirtualizedList(); + let response; + try { + await test.init(); + response = await test.test(); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }, parseInt(process.env.TEST_DURATION_TIME)); +}; +module.exports = exports = virtualizedListTest; diff --git a/bigbluebutton-html5/tests/puppeteer/virtualizedlist.test.js b/bigbluebutton-html5/tests/puppeteer/virtualizedlist.test.js index 8a1a599e9c32a1c0965935fc763073e113912e1b..5135a8c979a5a8312a76318eefe71d0073dc8cfb 100644 --- a/bigbluebutton-html5/tests/puppeteer/virtualizedlist.test.js +++ b/bigbluebutton-html5/tests/puppeteer/virtualizedlist.test.js @@ -1,17 +1,3 @@ -const VirtualizedList = require('./virtualizedlist/virtualize'); +const virtualizedListTest = require('./virtualizedlist.obj'); -describe('Virtualized List', () => { - test('Virtualized List Audio test', async () => { - const test = new VirtualizedList(); - let response; - try { - await test.init(); - response = await test.test(); - } catch (e) { - console.log(e); - } finally { - await test.close(); - } - expect(response).toBe(true); - }, parseInt(process.env.TEST_DURATION_TIME)); -}); +describe('Virtualized List', virtualizedListTest); diff --git a/bigbluebutton-html5/tests/puppeteer/webcam.obj.js b/bigbluebutton-html5/tests/puppeteer/webcam.obj.js new file mode 100644 index 0000000000000000000000000000000000000000..3212fe35bbe1707b71dbf1243432f131372a11c8 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/webcam.obj.js @@ -0,0 +1,38 @@ +const Share = require('./webcam/share'); +const Check = require('./webcam/check'); +const Page = require('./core/page'); + +const webcamTest = () => { + beforeEach(() => { + jest.setTimeout(30000); + }); + + test('Shares webcam', async () => { + const test = new Share(); + let response; + try { + await test.init(Page.getArgsWithVideo()); + response = await test.test(); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); + + test('Checks content of webcam', async () => { + const test = new Check(); + let response; + try { + await test.init(Page.getArgsWithVideo()); + response = await test.test(); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); +}; +module.exports = exports = webcamTest; diff --git a/bigbluebutton-html5/tests/puppeteer/webcam.test.js b/bigbluebutton-html5/tests/puppeteer/webcam.test.js index de311de7310e19a6d66ebc4aec7515ce6518518c..39e1f657dde205dad17537bf683a4adf90c49ebb 100644 --- a/bigbluebutton-html5/tests/puppeteer/webcam.test.js +++ b/bigbluebutton-html5/tests/puppeteer/webcam.test.js @@ -1,37 +1,3 @@ -const Share = require('./webcam/share'); -const Check = require('./webcam/check'); -const Page = require('./core/page'); +const webcamTest = require('./webcam.obj'); -describe('Webcam', () => { - beforeEach(() => { - jest.setTimeout(30000); - }); - - test('Shares webcam', async () => { - const test = new Share(); - let response; - try { - await test.init(Page.getArgsWithVideo()); - response = await test.test(); - } catch (e) { - console.log(e); - } finally { - await test.close(); - } - expect(response).toBe(true); - }); - - test('Checks content of webcam', async () => { - const test = new Check(); - let response; - try { - await test.init(Page.getArgsWithVideo()); - response = await test.test(); - } catch (e) { - console.log(e); - } finally { - await test.close(); - } - expect(response).toBe(true); - }); -}); +describe('Webcam', webcamTest); diff --git a/bigbluebutton-html5/tests/puppeteer/webcam/elements.js b/bigbluebutton-html5/tests/puppeteer/webcam/elements.js index b63e4b4bbebef8ac6ef49ed33a5ccb1ad2569d2c..b49ca0573f4c77adb4faccdc6ea37cc1c12f0b4e 100644 --- a/bigbluebutton-html5/tests/puppeteer/webcam/elements.js +++ b/bigbluebutton-html5/tests/puppeteer/webcam/elements.js @@ -4,3 +4,5 @@ exports.startSharingWebcam = 'button[aria-label="Start sharing"]'; exports.videoContainer = 'div[class^="videoListItem"]'; exports.webcamConnecting = '[class^="connecting-"]'; exports.presentationFullscreenButton = 'button[data-test="presentationFullscreenButton"]'; +exports.webcamItemTalkingUser = 'div[data-test="webcamItemTalkingUser"]'; +exports.webcamVideo = 'video[data-test="videoContainer"]'; diff --git a/bigbluebutton-html5/tests/puppeteer/webcam/util.js b/bigbluebutton-html5/tests/puppeteer/webcam/util.js index c2c84263dcae778112668700bd36ead9803ec2ee..22710e043b1cb620470aced949aec19d93ed7754 100644 --- a/bigbluebutton-html5/tests/puppeteer/webcam/util.js +++ b/bigbluebutton-html5/tests/puppeteer/webcam/util.js @@ -66,8 +66,13 @@ async function clickTestElement(element) { document.querySelectorAll(element)[0].click(); } +async function countTestElements(element) { + return await document.querySelectorAll(element).length; +} + exports.startAndCheckForWebcams = startAndCheckForWebcams; exports.webcamContentCheck = webcamContentCheck; exports.evaluateCheck = evaluateCheck; exports.getFullScreenWebcamButton = getFullScreenWebcamButton; exports.enableWebcam = enableWebcam; +exports.countTestElements = countTestElements; diff --git a/bigbluebutton-html5/tests/puppeteer/webcam/webcamlayout.js b/bigbluebutton-html5/tests/puppeteer/webcam/webcamlayout.js new file mode 100644 index 0000000000000000000000000000000000000000..24b571b7e3df09038cf492135d9c98ffcccdcb22 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/webcam/webcamlayout.js @@ -0,0 +1,21 @@ +const util = require('./util'); +const wle = require('./elements'); +const Page = require('../core/page'); + +class WebcamLayout extends Page { + constructor() { + super('webcam-layout-test'); + } + + async share() { + await this.joinMicrophone(this); + await util.enableWebcam(this); + } + + async test() { + await this.page.waitForSelector(wle.webcamConnecting); + await this.page.waitForSelector(wle.webcamVideo); + return await this.page.evaluate(util.countTestElements, wle.webcamItemTalkingUser) !== 0; + } +} +module.exports = exports = WebcamLayout; diff --git a/bigbluebutton-html5/tests/puppeteer/webcamlayout.obj.js b/bigbluebutton-html5/tests/puppeteer/webcamlayout.obj.js new file mode 100644 index 0000000000000000000000000000000000000000..93136dce28577cf8e734a7a5ff78790749cc98ae --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/webcamlayout.obj.js @@ -0,0 +1,24 @@ +const Page = require('./core/page'); +const webcamLayout = require('./webcam/webcamlayout'); + +const webcamLayoutTest = () => { + beforeEach(() => { + jest.setTimeout(50000); + }); + + test('Join Webcam and microphone', async () => { + const test = new webcamLayout(); + let response; + try { + await test.init(Page.getArgsWithAudioAndVideo()); + await test.share(); + response = await test.test(); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); +}; +module.exports = exports = webcamLayoutTest; diff --git a/bigbluebutton-html5/tests/puppeteer/webcamlayout.test.js b/bigbluebutton-html5/tests/puppeteer/webcamlayout.test.js new file mode 100644 index 0000000000000000000000000000000000000000..692ce0dd06f29b76cfd5fdcc641e57c87a407c3d --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/webcamlayout.test.js @@ -0,0 +1,3 @@ +const webcamLayoutTest = require('./webcamlayout.obj'); + +describe('Webcams Layout', webcamLayoutTest); diff --git a/bigbluebutton-html5/tests/puppeteer/whiteboard.obj.js b/bigbluebutton-html5/tests/puppeteer/whiteboard.obj.js new file mode 100644 index 0000000000000000000000000000000000000000..ec47850f137f894c45d03e66f6ff39fc235a4ed6 --- /dev/null +++ b/bigbluebutton-html5/tests/puppeteer/whiteboard.obj.js @@ -0,0 +1,24 @@ +const Page = require('./core/page'); +const Draw = require('./whiteboard/draw'); + +const whiteboardTest = () => { + beforeEach(() => { + jest.setTimeout(30000); + }); + + test('Draw rectangle', async () => { + const test = new Draw(); + let response; + try { + await test.init(Page.getArgs()); + await test.closeAudioModal(); + response = await test.test(); + } catch (e) { + console.log(e); + } finally { + await test.close(); + } + expect(response).toBe(true); + }); +}; +module.exports = exports = whiteboardTest; diff --git a/bigbluebutton-html5/tests/puppeteer/whiteboard.test.js b/bigbluebutton-html5/tests/puppeteer/whiteboard.test.js index 7dedf21410e72ca0eceae144a9b8d48468359ddc..46e252f5d1390ef73e8fb08efad5048d5a7b7a1c 100644 --- a/bigbluebutton-html5/tests/puppeteer/whiteboard.test.js +++ b/bigbluebutton-html5/tests/puppeteer/whiteboard.test.js @@ -1,23 +1,3 @@ -const Page = require('./core/page'); -const Draw = require('./whiteboard/draw'); +const whiteboardTest = require('./whiteboard.obj'); -describe('Whiteboard', () => { - beforeEach(() => { - jest.setTimeout(30000); - }); - - test('Draw rectangle', async () => { - const test = new Draw(); - let response; - try { - await test.init(Page.getArgs()); - await test.closeAudioModal(); - response = await test.test(); - } catch (e) { - console.log(e); - } finally { - await test.close(); - } - expect(response).toBe(true); - }); -}); +describe('Whiteboard', whiteboardTest); diff --git a/bigbluebutton-html5/transifex.sh b/bigbluebutton-html5/transifex.sh index c465c702a31ce50959583524cbaf576f9b7d7d4e..de1efe3eaa73f91ca62b80f75b450bf59da70587 100755 --- a/bigbluebutton-html5/transifex.sh +++ b/bigbluebutton-html5/transifex.sh @@ -13,7 +13,7 @@ else read -p "Enter Transifex Username: " USER read -p "password: " -s PW echo -e "\n----------------------------------\nchecking project info\n----------------------------------" - PROJECT_INFO=$( curl -L --user "$USER":"$PW" -X GET https://www.transifex.com/api/2/project/bigbluebutton-v22-html5-client/languages/ ) + PROJECT_INFO=$( curl -L --user "$USER":"$PW" -X GET https://www.transifex.com/api/2/project/bigbluebutton-v23-html5-client/languages/ ) if [ "$PROJECT_INFO" == "Authorization Required" ] then @@ -36,7 +36,7 @@ else if [ "$LOCALE" == "$SOURCE_LANGUAGE" ]; then continue # do not pull the source file fi - TRANSLATION=$(curl -L --user "$USER":"$PW" -X GET "https://www.transifex.com/api/2/project/bigbluebutton-v22-html5-client/resource/enjson/translation/$LOCALE/?mode=onlytranslated&file") + TRANSLATION=$(curl -L --user "$USER":"$PW" -X GET "https://www.transifex.com/api/2/project/bigbluebutton-v23-html5-client/resource/enjson/translation/$LOCALE/?mode=onlytranslated&file") NO_EMPTY_STRINGS=$(echo "$TRANSLATION" | sed '/: *\"\"/D' | sed '/}$/D') if [ $(echo "$NO_EMPTY_STRINGS" | wc -l) == 1 ] then @@ -50,7 +50,7 @@ else fi done else - TRANSLATION=$(curl -L --user "$USER":"$PW" -X GET "https://www.transifex.com/api/2/project/bigbluebutton-v22-html5-client/resource/enjson/translation/$ARG/?mode=onlytranslated&file") + TRANSLATION=$(curl -L --user "$USER":"$PW" -X GET "https://www.transifex.com/api/2/project/bigbluebutton-v23-html5-client/resource/enjson/translation/$ARG/?mode=onlytranslated&file") if [ "$TRANSLATION" == "Not Found" ] then echo -e "${RED}Err${NC}: Translations not found for locale ->${RED}$ARG${NC}<-" diff --git a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties index 30efaa2cbd490e0f58549bea369591eae71d2ad8..6c73b6f77b1d87437116c8b191b8e9ff2f8a13ff 100755 --- a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties +++ b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties @@ -161,8 +161,8 @@ defaultGuestPolicy=ALWAYS_ACCEPT # # native2ascii -encoding UTF8 bigbluebutton.properties bigbluebutton.properties # -defaultWelcomeMessage=Welcome to <b>%%CONFNAME%%</b>!<br><br>For help on using BigBlueButton see these (short) <a href="event:https://bigbluebutton.org/html5"><u>tutorial videos</u></a>.<br><br>To join the audio bridge click the phone button. Use a headset to avoid causing background noise for others. -defaultWelcomeMessageFooter=This server is running <a href="https://docs.bigbluebutton.org" target="_blank"><u>BigBlueButton</u></a>. +defaultWelcomeMessage=Welcome to <b>%%CONFNAME%%</b>!<br><br>For help on using BigBlueButton see these (short) <a href="https://www.bigbluebutton.org/html5"><u>tutorial videos</u></a>.<br><br>To join the audio bridge click the phone button. Use a headset to avoid causing background noise for others. +defaultWelcomeMessageFooter=This server is running <a href="https://docs.bigbluebutton.org/" target="_blank"><u>BigBlueButton</u></a>. # Default maximum number of users a meeting can have. # Current default is 0 (meeting doesn't have a user limit). @@ -172,19 +172,10 @@ defaultMaxUsers=0 # Current default is 0 (meeting doesn't end). defaultMeetingDuration=0 -# Number of minutes elapse of no activity before -# ending the meeting. Default zero (0) to disable -# check. -maxInactivityTimeoutMinutes=0 - # Number of minutes to logout client if user # isn't responsive clientLogoutTimerInMinutes=0 -# Send warning to moderators to warn that -# meeting would be ended due to inactivity -warnMinutesBeforeMax=5 - # End meeting if no user joined within # a period of time after meeting created. meetingExpireIfNoUserJoinedInMinutes=5 @@ -250,10 +241,10 @@ defaultClientUrl=${bigbluebutton.web.serverURL}/client/BigBlueButton.html allowRequestsWithoutSession=false # Force all attendees to join the meeting using the HTML5 client -attendeesJoinViaHTML5Client=false +attendeesJoinViaHTML5Client=true # Force all moderators to join the meeting using the HTML5 client -moderatorsJoinViaHTML5Client=false +moderatorsJoinViaHTML5Client=true # The url of the BigBlueButton HTML5 client. Users will be redirected here when # successfully joining the meeting. @@ -263,10 +254,9 @@ html5ClientUrl=${bigbluebutton.web.serverURL}/html5client/join # The url for where the guest will poll if approved to join or not. defaultGuestWaitURL=${bigbluebutton.web.serverURL}/html5client/guestWait -# The default avatar image to display if nothing is passed on the JOIN API (avatarURL) -# call. This avatar is displayed if the user isn't sharing the webcam and -# the option (displayAvatar) is enabled in config.xml -defaultAvatarURL=${bigbluebutton.web.serverURL}/client/avatar.png +# The default avatar image to display. +useDefaultAvatar=false +defaultAvatarURL=${bigbluebutton.web.serverURL}/html5client/resources/images/avatar.png # The URL of the default configuration defaultConfigURL=${bigbluebutton.web.serverURL}/client/conf/config.xml @@ -363,3 +353,8 @@ lockSettingsLockOnJoinConfigurable=false allowDuplicateExtUserid=true defaultTextTrackUrl=${bigbluebutton.web.serverURL}/bigbluebutton + +# Param to end the meeting when there are no moderators after a certain period of time. +# Needed for classes where teacher gets disconnected and can't get back in. Prevents +# students from running amok. +endWhenNoModerator=false diff --git a/bigbluebutton-web/grails-app/conf/spring/resources.xml b/bigbluebutton-web/grails-app/conf/spring/resources.xml index ff695f99cd1ebeb348df2b1418102d7ade3c9b73..52c179bd114141659fe1c8ec706b887d11b9c36e 100755 --- a/bigbluebutton-web/grails-app/conf/spring/resources.xml +++ b/bigbluebutton-web/grails-app/conf/spring/resources.xml @@ -133,11 +133,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <property name="autoStartRecording" value="${autoStartRecording}"/> <property name="allowStartStopRecording" value="${allowStartStopRecording}"/> <property name="webcamsOnlyForModerator" value="${webcamsOnlyForModerator}"/> + <property name="useDefaultAvatar" value="${useDefaultAvatar}"/> <property name="defaultAvatarURL" value="${defaultAvatarURL}"/> <property name="defaultConfigURL" value="${defaultConfigURL}"/> <property name="defaultGuestPolicy" value="${defaultGuestPolicy}"/> - <property name="maxInactivityTimeoutMinutes" value="${maxInactivityTimeoutMinutes}"/> - <property name="warnMinutesBeforeMax" value="${warnMinutesBeforeMax}"/> <property name="meetingExpireIfNoUserJoinedInMinutes" value="${meetingExpireIfNoUserJoinedInMinutes}"/> <property name="meetingExpireWhenLastUserLeftInMinutes" value="${meetingExpireWhenLastUserLeftInMinutes}"/> <property name="userInactivityInspectTimerInMinutes" value="${userInactivityInspectTimerInMinutes}"/> @@ -160,6 +159,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <property name="lockSettingsLockOnJoin" value="${lockSettingsLockOnJoin}"/> <property name="lockSettingsLockOnJoinConfigurable" value="${lockSettingsLockOnJoinConfigurable}"/> <property name="allowDuplicateExtUserid" value="${allowDuplicateExtUserid}"/> + <property name="endWhenNoModerator" value="${endWhenNoModerator}"/> </bean> <import resource="doc-conversion.xml"/> diff --git a/labs/vertx-akka/src/main/scala/org/bigbluebutton/client/meeting/AllowedMessageNames.scala b/labs/vertx-akka/src/main/scala/org/bigbluebutton/client/meeting/AllowedMessageNames.scala index 92a47f39cfc1c1d1e36f4770effad555316c336d..cd7b18454cfe328daeb889f244a1c4ae8efacf8a 100755 --- a/labs/vertx-akka/src/main/scala/org/bigbluebutton/client/meeting/AllowedMessageNames.scala +++ b/labs/vertx-akka/src/main/scala/org/bigbluebutton/client/meeting/AllowedMessageNames.scala @@ -22,7 +22,6 @@ object AllowedMessageNames { UserBroadcastCamStopMsg.NAME, LogoutAndEndMeetingCmdMsg.NAME, GetRecordingStatusReqMsg.NAME, - MeetingActivityResponseCmdMsg.NAME, SetRecordingStatusCmdMsg.NAME, EjectUserFromMeetingCmdMsg.NAME, IsMeetingMutedReqMsg.NAME, @@ -86,14 +85,6 @@ object AllowedMessageNames { UpdateCaptionOwnerPubMsg.NAME, EditCaptionHistoryPubMsg.NAME, - // Shared Notes Messages - GetSharedNotesPubMsg.NAME, - CreateSharedNoteReqMsg.NAME, - DestroySharedNoteReqMsg.NAME, - UpdateSharedNoteReqMsg.NAME, - SyncSharedNotePubMsg.NAME, - ClearSharedNotePubMsg.NAME, - // Layout Messages GetCurrentLayoutReqMsg.NAME, BroadcastLayoutMsg.NAME, diff --git a/record-and-playback/core/lib/recordandplayback/events_archiver.rb b/record-and-playback/core/lib/recordandplayback/events_archiver.rb index 9b1198fe66bd2ad50659f91e97f63853c7b91130..fc7269c743cf6e785666406e279e78480bc73894 100755 --- a/record-and-playback/core/lib/recordandplayback/events_archiver.rb +++ b/record-and-playback/core/lib/recordandplayback/events_archiver.rb @@ -228,8 +228,13 @@ module BigBlueButton # Apply a cleanup that removes certain ranges of special # control characters from user-provided text + # Based on https://www.w3.org/TR/xml/#charsets + # Remove all Unicode characters not valid in XML, and also remove + # discouraged characters (includes control characters) in the U+0000-U+FFFF + # range (the higher values are just undefined characters, and are unlikely + # to cause issues). def strip_control_chars(str) - str.tr("\x00-\x08\x0B\x0C\x0E-\x1F\x7F", '') + str.scrub.tr("\x00-\x08\x0B\x0C\x0E-\x1F\x7F\uFDD0-\uFDEF\uFFFE\uFFFF", '') end def store_events(meeting_id, events_file, break_timestamp) @@ -256,7 +261,7 @@ module BigBlueButton end meeting_metadata = @redis.metadata_for(meeting_id) - return if meeting_metadata.nil? + return if meeting_metadata.nil? || meeting_metadata.empty? # Fill in/update the top-level meeting element if meeting.nil? diff --git a/record-and-playback/core/scripts/rap-starter.rb b/record-and-playback/core/scripts/rap-starter.rb index 932c206eefb74b76bbc158275d03878b7a379e9f..2bd2686bf7594cbe3cc7cdf0a4dc1e31b80825f4 100755 --- a/record-and-playback/core/scripts/rap-starter.rb +++ b/record-and-playback/core/scripts/rap-starter.rb @@ -92,7 +92,7 @@ begin # Check for missed "ended" .done files ended_done_files = Dir.glob("#{ended_status_dir}/*.done") ended_done_files.each do |ended_done_file| - keep_meeting_events(recording_id, ended_done_file) + keep_meeting_events(recording_dir, ended_done_file) end # Check for missed "recorded" .done files diff --git a/record-and-playback/presentation/playback/presentation/2.0/lib/popcorn.chattimeline.js b/record-and-playback/presentation/playback/presentation/2.0/lib/popcorn.chattimeline.js index 0838bd926819470d24fa000e5be8e1ea4e0cbdcf..aeb9b586dc3c5f852b6680510d2bcc06bbecc94b 100755 --- a/record-and-playback/presentation/playback/presentation/2.0/lib/popcorn.chattimeline.js +++ b/record-and-playback/presentation/playback/presentation/2.0/lib/popcorn.chattimeline.js @@ -58,7 +58,7 @@ i++; - contentDiv.innerHTML = "<strong>" + options.name + ":</strong>" + options.message; + contentDiv.innerHTML = "<strong>" + options.name + ":</strong>" + nl2br(options.message); //If chat message contains a link, we add to it a target attribute //So when the user clicks on it, it opens in a new tab diff --git a/record-and-playback/presentation/playback/presentation/2.0/playback.js b/record-and-playback/presentation/playback/presentation/2.0/playback.js index e182b01ce6a944539cc401bd82e776d09326dad9..6336734307c7a15aca6c0de6515fbce4494dee7c 100755 --- a/record-and-playback/presentation/playback/presentation/2.0/playback.js +++ b/record-and-playback/presentation/playback/presentation/2.0/playback.js @@ -130,6 +130,43 @@ function replaceTimeOnURL(secs) { window.history.replaceState({}, "", newUrl); }; +/* + * From: https://locutus.io/php/strings/nl2br/ + */ +function nl2br (str, isXhtml) { + // discuss at: https://locutus.io/php/nl2br/ + // original by: Kevin van Zonneveld (https://kvz.io) + // improved by: Philip Peterson + // improved by: Onno Marsman (https://twitter.com/onnomarsman) + // improved by: Atli Þór + // improved by: Brett Zamir (https://brett-zamir.me) + // improved by: Maximusya + // bugfixed by: Onno Marsman (https://twitter.com/onnomarsman) + // bugfixed by: Kevin van Zonneveld (https://kvz.io) + // bugfixed by: Reynier de la Rosa (https://scriptinside.blogspot.com.es/) + // input by: Brett Zamir (https://brett-zamir.me) + // example 1: nl2br('Kevin\nvan\nZonneveld') + // returns 1: 'Kevin<br />\nvan<br />\nZonneveld' + // example 2: nl2br("\nOne\nTwo\n\nThree\n", false) + // returns 2: '<br>\nOne<br>\nTwo<br>\n<br>\nThree<br>\n' + // example 3: nl2br("\nOne\nTwo\n\nThree\n", true) + // returns 3: '<br />\nOne<br />\nTwo<br />\n<br />\nThree<br />\n' + // example 4: nl2br(null) + // returns 4: '' + + // Some latest browsers when str is null return and unexpected null value + if (typeof str === 'undefined' || str === null) { + return '' + } + + // Adjust comment to avoid issue on locutus.io display + var breakTag = (isXhtml || typeof isXhtml === 'undefined') ? '<br ' + '/>' : '<br>' + + return (str + '') + .replace(/(\r\n|\n\r|\r|\n)/g, breakTag + '$1') +} + + /* * Sets the title attribute in a thumbnail. */