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("&", "&amp;").replace("<", "&lt;")
-          .replace(">", "&gt;").replace("\n", "&para;<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 &egrave; troppo grande. Per favore dividilo in pi&ugrave; 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": "あと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": "ペン",
@@ -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.
  */